commit 6d1028c0b4fceeafd3a890850ed4ebc20dde19cf
parent a41e3ea751bf572995a11e81e5aa7065fae2160b
Author: Marc Stibane <marc@taler.net>
Date: Wed, 21 Jun 2023 09:29:57 +0200
Made Model a Singleton
Diffstat:
52 files changed, 1685 insertions(+), 1637 deletions(-)
diff --git a/TalerWallet.xcodeproj/project.pbxproj b/TalerWallet.xcodeproj/project.pbxproj
@@ -8,10 +8,15 @@
/* Begin PBXBuildFile section */
4E16E12329F3BB99008B9C86 /* CurrencyFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E16E12229F3BB99008B9C86 /* CurrencyFormatter.swift */; };
- 4E363CBC2A237E0900D7E98C /* URL+iban.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E363CBB2A237E0900D7E98C /* URL+iban.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 */; };
4E363CC22A2621C200D7E98C /* LocalizedAlertError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E363CC12A2621C200D7E98C /* LocalizedAlertError.swift */; };
+ 4E3B4BC12A41E6C200CC88B8 /* P2pReceiveURIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3B4BC02A41E6C200CC88B8 /* P2pReceiveURIView.swift */; };
+ 4E3B4BC32A42252300CC88B8 /* P2pAcceptDone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift */; };
+ 4E3B4BC52A428AF700CC88B8 /* Model+Balances.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3B4BC42A428AF700CC88B8 /* Model+Balances.swift */; };
+ 4E3B4BC72A429F2A00CC88B8 /* View+Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3B4BC62A429F2A00CC88B8 /* View+Notification.swift */; };
+ 4E3B4BC92A42BC4800CC88B8 /* Model+Exchange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E3B4BC82A42BC4800CC88B8 /* Model+Exchange.swift */; };
4E40E0BE29F25ABB00B85369 /* SendAmount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E40E0BD29F25ABB00B85369 /* SendAmount.swift */; };
4E50B3502A1BEE8000F9F01C /* ManualWithdraw.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E50B34F2A1BEE8000F9F01C /* ManualWithdraw.swift */; };
4E53A33729F50B7B00830EC2 /* CurrencyField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E53A33629F50B7B00830EC2 /* CurrencyField.swift */; };
@@ -27,7 +32,7 @@
4E8E25332A1CD39700A27BFA /* EqualIconWidthDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8E25322A1CD39700A27BFA /* EqualIconWidthDomain.swift */; };
4E9320432A14F6EA00A87B0E /* WalletColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9320422A14F6EA00A87B0E /* WalletColors.swift */; };
4E9320452A1645B600A87B0E /* RequestPayment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9320442A1645B600A87B0E /* RequestPayment.swift */; };
- 4E9320472A164BC700A87B0E /* ReceivePurpose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* ReceivePurpose.swift */; };
+ 4E9320472A164BC700A87B0E /* PaymentPurpose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E9320462A164BC700A87B0E /* PaymentPurpose.swift */; };
4E9796902A3765ED006F73BC /* AgePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E97968F2A3765ED006F73BC /* AgePicker.swift */; };
4EA1ABBE29A3833A008821EA /* PublicConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA1ABBD29A3833A008821EA /* PublicConstants.swift */; };
4EA551252A2C923600FEC9A8 /* CurrencyInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EA551242A2C923600FEC9A8 /* CurrencyInputView.swift */; };
@@ -47,33 +52,30 @@
4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095072989CB7C0043A8A1 /* TalerStrings.swift */; };
4EB0950B2989CB7C0043A8A1 /* View+dismissTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095082989CB7C0043A8A1 /* View+dismissTop.swift */; };
4EB0950E2989CB9A0043A8A1 /* quickjs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0950D2989CB9A0043A8A1 /* quickjs.swift */; };
- 4EB095152989CBB00043A8A1 /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095102989CBB00043A8A1 /* SettingsModel.swift */; };
+ 4EB095152989CBB00043A8A1 /* Model+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095102989CBB00043A8A1 /* Model+Settings.swift */; };
4EB095162989CBB00043A8A1 /* WalletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095112989CBB00043A8A1 /* WalletModel.swift */; };
- 4EB095192989CBB00043A8A1 /* WalletInitModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095142989CBB00043A8A1 /* WalletInitModel.swift */; };
4EB0951F2989CBCB0043A8A1 /* WalletBackendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0951B2989CBCB0043A8A1 /* WalletBackendRequest.swift */; };
4EB095202989CBCB0043A8A1 /* WalletCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0951C2989CBCB0043A8A1 /* WalletCore.swift */; };
4EB095212989CBCB0043A8A1 /* WalletBackendError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0951D2989CBCB0043A8A1 /* WalletBackendError.swift */; };
4EB095222989CBCB0043A8A1 /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0951E2989CBCB0043A8A1 /* Transaction.swift */; };
4EB0954F2989CBFE0043A8A1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095252989CBFE0043A8A1 /* SettingsView.swift */; };
4EB095502989CBFE0043A8A1 /* SettingsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095262989CBFE0043A8A1 /* SettingsItem.swift */; };
- 4EB095512989CBFE0043A8A1 /* ExchangeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095282989CBFE0043A8A1 /* ExchangeModel.swift */; };
4EB095522989CBFE0043A8A1 /* ExchangeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095292989CBFE0043A8A1 /* ExchangeListView.swift */; };
4EB095532989CBFE0043A8A1 /* PaymentAcceptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0952B2989CBFE0043A8A1 /* PaymentAcceptView.swift */; };
- 4EB095542989CBFE0043A8A1 /* PaymentURIModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0952C2989CBFE0043A8A1 /* PaymentURIModel.swift */; };
+ 4EB095542989CBFE0043A8A1 /* Model+Payment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0952C2989CBFE0043A8A1 /* Model+Payment.swift */; };
4EB095552989CBFE0043A8A1 /* PaymentURIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0952D2989CBFE0043A8A1 /* PaymentURIView.swift */; };
4EB095562989CBFE0043A8A1 /* TransactionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0952F2989CBFE0043A8A1 /* TransactionsListView.swift */; };
4EB095572989CBFE0043A8A1 /* TransactionRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095302989CBFE0043A8A1 /* TransactionRowView.swift */; };
4EB095582989CBFE0043A8A1 /* TransactionDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095312989CBFE0043A8A1 /* TransactionDetailView.swift */; };
- 4EB095592989CBFE0043A8A1 /* TransactionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095322989CBFE0043A8A1 /* TransactionsModel.swift */; };
+ 4EB095592989CBFE0043A8A1 /* Model+Transactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095322989CBFE0043A8A1 /* Model+Transactions.swift */; };
4EB0955A2989CBFE0043A8A1 /* URLSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095332989CBFE0043A8A1 /* URLSheet.swift */; };
- 4EB0955B2989CBFE0043A8A1 /* BalancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095352989CBFE0043A8A1 /* BalancesModel.swift */; };
4EB0955C2989CBFE0043A8A1 /* BalanceRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095362989CBFE0043A8A1 /* BalanceRowView.swift */; };
4EB0955D2989CBFE0043A8A1 /* BalancesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095372989CBFE0043A8A1 /* BalancesListView.swift */; };
4EB0955E2989CBFE0043A8A1 /* PendingRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095382989CBFE0043A8A1 /* PendingRowView.swift */; };
4EB0955F2989CBFE0043A8A1 /* WalletEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095392989CBFE0043A8A1 /* WalletEmptyView.swift */; };
4EB095602989CBFE0043A8A1 /* BalancesSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953A2989CBFE0043A8A1 /* BalancesSectionView.swift */; };
4EB095612989CBFE0043A8A1 /* WithdrawURIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953C2989CBFE0043A8A1 /* WithdrawURIView.swift */; };
- 4EB095622989CBFE0043A8A1 /* WithdrawModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift */; };
+ 4EB095622989CBFE0043A8A1 /* Model+Withdraw.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953D2989CBFE0043A8A1 /* Model+Withdraw.swift */; };
4EB095642989CBFE0043A8A1 /* WithdrawProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0953F2989CBFE0043A8A1 /* WithdrawProgressView.swift */; };
4EB095652989CBFE0043A8A1 /* WithdrawTOSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095402989CBFE0043A8A1 /* WithdrawTOSView.swift */; };
4EB095662989CBFE0043A8A1 /* SideBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095422989CBFE0043A8A1 /* SideBarView.swift */; };
@@ -84,7 +86,7 @@
4EB0956B2989CBFE0043A8A1 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095482989CBFE0043A8A1 /* TextFieldAlert.swift */; };
4EB0956C2989CBFE0043A8A1 /* AmountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB095492989CBFE0043A8A1 /* AmountView.swift */; };
4EB0956D2989CBFE0043A8A1 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954A2989CBFE0043A8A1 /* LoadingView.swift */; };
- 4EB0956E2989CBFE0043A8A1 /* PendingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954C2989CBFE0043A8A1 /* PendingModel.swift */; };
+ 4EB0956E2989CBFE0043A8A1 /* Model+Pending.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954C2989CBFE0043A8A1 /* Model+Pending.swift */; };
4EB0956F2989CBFE0043A8A1 /* PendingOpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift */; };
4EB095702989CBFE0043A8A1 /* PendingOpsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB0954E2989CBFE0043A8A1 /* PendingOpsListView.swift */; };
4EB3136129FEE79B007D68BC /* SendNow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3136029FEE79B007D68BC /* SendNow.swift */; };
@@ -92,7 +94,7 @@
4EBA82AB2A3EB2CA00E5F39A /* TransactionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA82AA2A3EB2CA00E5F39A /* TransactionButton.swift */; };
4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EBA82AC2A3F580500E5F39A /* QuiteSomeCoins.swift */; };
4EC90C782A1B528B0071DC58 /* ExchangeSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */; };
- 4ECB62802A0BA6DF004ABBB7 /* Peer2peerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECB627F2A0BA6DF004ABBB7 /* Peer2peerModel.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 */; };
4ED2F94B2A278F5100453B40 /* ThreeAmounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED2F94A2A278F5100453B40 /* ThreeAmounts.swift */; };
4EEC157329F8242800D46A03 /* QRGeneratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EEC157229F8242800D46A03 /* QRGeneratorView.swift */; };
@@ -135,11 +137,16 @@
/* Begin PBXFileReference section */
4E16E12229F3BB99008B9C86 /* CurrencyFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyFormatter.swift; sourceTree = "<group>"; };
- 4E363CBB2A237E0900D7E98C /* URL+iban.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+iban.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>"; };
4E363CC12A2621C200D7E98C /* LocalizedAlertError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizedAlertError.swift; sourceTree = "<group>"; };
4E3AE7EF29A7E8F40070BEC4 /* Taler Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Taler Wallet.entitlements"; sourceTree = "<group>"; };
+ 4E3B4BC02A41E6C200CC88B8 /* P2pReceiveURIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = P2pReceiveURIView.swift; sourceTree = "<group>"; };
+ 4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = P2pAcceptDone.swift; sourceTree = "<group>"; };
+ 4E3B4BC42A428AF700CC88B8 /* Model+Balances.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Balances.swift"; sourceTree = "<group>"; };
+ 4E3B4BC62A429F2A00CC88B8 /* View+Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Notification.swift"; sourceTree = "<group>"; };
+ 4E3B4BC82A42BC4800CC88B8 /* Model+Exchange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Exchange.swift"; sourceTree = "<group>"; };
4E40E0BD29F25ABB00B85369 /* SendAmount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SendAmount.swift; path = TalerWallet1/Views/Peer2peer/SendAmount.swift; sourceTree = SOURCE_ROOT; };
4E50B34F2A1BEE8000F9F01C /* ManualWithdraw.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManualWithdraw.swift; sourceTree = "<group>"; };
4E53A33629F50B7B00830EC2 /* CurrencyField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyField.swift; sourceTree = "<group>"; };
@@ -156,7 +163,7 @@
4E8E25322A1CD39700A27BFA /* EqualIconWidthDomain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EqualIconWidthDomain.swift; sourceTree = "<group>"; };
4E9320422A14F6EA00A87B0E /* WalletColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletColors.swift; sourceTree = "<group>"; };
4E9320442A1645B600A87B0E /* RequestPayment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestPayment.swift; sourceTree = "<group>"; };
- 4E9320462A164BC700A87B0E /* ReceivePurpose.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReceivePurpose.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>"; };
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>"; };
@@ -176,33 +183,30 @@
4EB095072989CB7C0043A8A1 /* TalerStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TalerStrings.swift; sourceTree = "<group>"; };
4EB095082989CB7C0043A8A1 /* View+dismissTop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+dismissTop.swift"; sourceTree = "<group>"; };
4EB0950D2989CB9A0043A8A1 /* quickjs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = quickjs.swift; sourceTree = "<group>"; };
- 4EB095102989CBB00043A8A1 /* SettingsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = "<group>"; };
+ 4EB095102989CBB00043A8A1 /* Model+Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Settings.swift"; sourceTree = "<group>"; };
4EB095112989CBB00043A8A1 /* WalletModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletModel.swift; sourceTree = "<group>"; };
- 4EB095142989CBB00043A8A1 /* WalletInitModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletInitModel.swift; sourceTree = "<group>"; };
4EB0951B2989CBCB0043A8A1 /* WalletBackendRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletBackendRequest.swift; sourceTree = "<group>"; };
4EB0951C2989CBCB0043A8A1 /* WalletCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletCore.swift; sourceTree = "<group>"; };
4EB0951D2989CBCB0043A8A1 /* WalletBackendError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletBackendError.swift; sourceTree = "<group>"; };
4EB0951E2989CBCB0043A8A1 /* Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = "<group>"; };
4EB095252989CBFE0043A8A1 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
4EB095262989CBFE0043A8A1 /* SettingsItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsItem.swift; sourceTree = "<group>"; };
- 4EB095282989CBFE0043A8A1 /* ExchangeModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExchangeModel.swift; sourceTree = "<group>"; };
4EB095292989CBFE0043A8A1 /* ExchangeListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExchangeListView.swift; sourceTree = "<group>"; };
4EB0952B2989CBFE0043A8A1 /* PaymentAcceptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentAcceptView.swift; sourceTree = "<group>"; };
- 4EB0952C2989CBFE0043A8A1 /* PaymentURIModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentURIModel.swift; sourceTree = "<group>"; };
+ 4EB0952C2989CBFE0043A8A1 /* Model+Payment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Payment.swift"; sourceTree = "<group>"; };
4EB0952D2989CBFE0043A8A1 /* PaymentURIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentURIView.swift; sourceTree = "<group>"; };
4EB0952F2989CBFE0043A8A1 /* TransactionsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsListView.swift; sourceTree = "<group>"; };
4EB095302989CBFE0043A8A1 /* TransactionRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionRowView.swift; sourceTree = "<group>"; };
4EB095312989CBFE0043A8A1 /* TransactionDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionDetailView.swift; sourceTree = "<group>"; };
- 4EB095322989CBFE0043A8A1 /* TransactionsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsModel.swift; sourceTree = "<group>"; };
+ 4EB095322989CBFE0043A8A1 /* Model+Transactions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Transactions.swift"; sourceTree = "<group>"; };
4EB095332989CBFE0043A8A1 /* URLSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSheet.swift; sourceTree = "<group>"; };
- 4EB095352989CBFE0043A8A1 /* BalancesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalancesModel.swift; sourceTree = "<group>"; };
4EB095362989CBFE0043A8A1 /* BalanceRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalanceRowView.swift; sourceTree = "<group>"; };
4EB095372989CBFE0043A8A1 /* BalancesListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalancesListView.swift; sourceTree = "<group>"; };
4EB095382989CBFE0043A8A1 /* PendingRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingRowView.swift; sourceTree = "<group>"; };
4EB095392989CBFE0043A8A1 /* WalletEmptyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletEmptyView.swift; sourceTree = "<group>"; };
4EB0953A2989CBFE0043A8A1 /* BalancesSectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalancesSectionView.swift; sourceTree = "<group>"; };
4EB0953C2989CBFE0043A8A1 /* WithdrawURIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WithdrawURIView.swift; sourceTree = "<group>"; };
- 4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WithdrawModel.swift; sourceTree = "<group>"; };
+ 4EB0953D2989CBFE0043A8A1 /* Model+Withdraw.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Withdraw.swift"; sourceTree = "<group>"; };
4EB0953F2989CBFE0043A8A1 /* WithdrawProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WithdrawProgressView.swift; sourceTree = "<group>"; };
4EB095402989CBFE0043A8A1 /* WithdrawTOSView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WithdrawTOSView.swift; sourceTree = "<group>"; };
4EB095422989CBFE0043A8A1 /* SideBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideBarView.swift; sourceTree = "<group>"; };
@@ -213,7 +217,7 @@
4EB095482989CBFE0043A8A1 /* TextFieldAlert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; };
4EB095492989CBFE0043A8A1 /* AmountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmountView.swift; sourceTree = "<group>"; };
4EB0954A2989CBFE0043A8A1 /* LoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = "<group>"; };
- 4EB0954C2989CBFE0043A8A1 /* PendingModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingModel.swift; sourceTree = "<group>"; };
+ 4EB0954C2989CBFE0043A8A1 /* Model+Pending.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Model+Pending.swift"; sourceTree = "<group>"; };
4EB0954D2989CBFE0043A8A1 /* PendingOpView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingOpView.swift; sourceTree = "<group>"; };
4EB0954E2989CBFE0043A8A1 /* PendingOpsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PendingOpsListView.swift; sourceTree = "<group>"; };
4EB3136029FEE79B007D68BC /* SendNow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendNow.swift; sourceTree = "<group>"; };
@@ -221,7 +225,7 @@
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>"; };
4EC90C772A1B528B0071DC58 /* ExchangeSectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExchangeSectionView.swift; sourceTree = "<group>"; };
- 4ECB627F2A0BA6DF004ABBB7 /* Peer2peerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Peer2peerModel.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>"; };
4ED2F94A2A278F5100453B40 /* ThreeAmounts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeAmounts.swift; sourceTree = "<group>"; };
4EEC157229F8242800D46A03 /* QRGeneratorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRGeneratorView.swift; sourceTree = "<group>"; };
@@ -264,6 +268,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 4E3B4BBF2A41E64000CC88B8 /* P2P_Sheets */ = {
+ isa = PBXGroup;
+ children = (
+ 4E3B4BC02A41E6C200CC88B8 /* P2pReceiveURIView.swift */,
+ 4E3B4BC22A42252300CC88B8 /* P2pAcceptDone.swift */,
+ );
+ path = P2P_Sheets;
+ sourceTree = "<group>";
+ };
4EB094EE298979840043A8A1 /* TalerWallet1 */ = {
isa = PBXGroup;
children = (
@@ -319,7 +332,8 @@
4EB095062989CB7C0043A8A1 /* TalerDater.swift */,
4EB095072989CB7C0043A8A1 /* TalerStrings.swift */,
4EB095082989CB7C0043A8A1 /* View+dismissTop.swift */,
- 4E363CBB2A237E0900D7E98C /* URL+iban.swift */,
+ 4E3B4BC62A429F2A00CC88B8 /* View+Notification.swift */,
+ 4E363CBB2A237E0900D7E98C /* URL+id+iban.swift */,
4E9320422A14F6EA00A87B0E /* WalletColors.swift */,
4E8E25322A1CD39700A27BFA /* EqualIconWidthDomain.swift */,
);
@@ -338,15 +352,14 @@
isa = PBXGroup;
children = (
4EB095112989CBB00043A8A1 /* WalletModel.swift */,
- 4EB095142989CBB00043A8A1 /* WalletInitModel.swift */,
- 4EB095352989CBFE0043A8A1 /* BalancesModel.swift */,
- 4EB095282989CBFE0043A8A1 /* ExchangeModel.swift */,
- 4ECB627F2A0BA6DF004ABBB7 /* Peer2peerModel.swift */,
- 4EB0954C2989CBFE0043A8A1 /* PendingModel.swift */,
- 4EB0952C2989CBFE0043A8A1 /* PaymentURIModel.swift */,
- 4EB095102989CBB00043A8A1 /* SettingsModel.swift */,
- 4EB095322989CBFE0043A8A1 /* TransactionsModel.swift */,
- 4EB0953D2989CBFE0043A8A1 /* WithdrawModel.swift */,
+ 4E3B4BC42A428AF700CC88B8 /* Model+Balances.swift */,
+ 4E3B4BC82A42BC4800CC88B8 /* Model+Exchange.swift */,
+ 4ECB627F2A0BA6DF004ABBB7 /* Model+P2P.swift */,
+ 4EB0954C2989CBFE0043A8A1 /* Model+Pending.swift */,
+ 4EB0952C2989CBFE0043A8A1 /* Model+Payment.swift */,
+ 4EB095102989CBB00043A8A1 /* Model+Settings.swift */,
+ 4EB095322989CBFE0043A8A1 /* Model+Transactions.swift */,
+ 4EB0953D2989CBFE0043A8A1 /* Model+Withdraw.swift */,
);
path = Model;
sourceTree = "<group>";
@@ -493,7 +506,7 @@
4E7940DD29FC307C00A9AEA1 /* SendPurpose.swift */,
4EB3136029FEE79B007D68BC /* SendNow.swift */,
4E9320442A1645B600A87B0E /* RequestPayment.swift */,
- 4E9320462A164BC700A87B0E /* ReceivePurpose.swift */,
+ 4E9320462A164BC700A87B0E /* PaymentPurpose.swift */,
);
path = Peer2peer;
sourceTree = "<group>";
@@ -505,6 +518,7 @@
4EEC157929F9427F00D46A03 /* QRSheet.swift */,
4E753A072A0B6A5F002D9328 /* ShareSheet.swift */,
4EB095332989CBFE0043A8A1 /* URLSheet.swift */,
+ 4E3B4BBF2A41E64000CC88B8 /* P2P_Sheets */,
);
path = Sheets;
sourceTree = "<group>";
@@ -699,7 +713,6 @@
buildActionMask = 2147483647;
files = (
4ECB62822A0BB01D004ABBB7 /* SelectDays.swift in Sources */,
- 4EB095512989CBFE0043A8A1 /* ExchangeModel.swift in Sources */,
4E9796902A3765ED006F73BC /* AgePicker.swift in Sources */,
4EB095032989C9BC0043A8A1 /* Controller.swift in Sources */,
4EB095682989CBFE0043A8A1 /* MainView.swift in Sources */,
@@ -714,15 +727,15 @@
4EB095532989CBFE0043A8A1 /* PaymentAcceptView.swift in Sources */,
4EB095212989CBCB0043A8A1 /* WalletBackendError.swift in Sources */,
4EB0955E2989CBFE0043A8A1 /* PendingRowView.swift in Sources */,
- 4EB0955B2989CBFE0043A8A1 /* BalancesModel.swift in Sources */,
4EB0956D2989CBFE0043A8A1 /* LoadingView.swift in Sources */,
4E50B3502A1BEE8000F9F01C /* ManualWithdraw.swift in Sources */,
+ 4E3B4BC92A42BC4800CC88B8 /* Model+Exchange.swift in Sources */,
4E5A88F52A38A4FD00072618 /* QRCodeDetailView.swift in Sources */,
4E87C8732A31CB7F001C6406 /* TransactionsEmptyView.swift in Sources */,
4E87C8752A34B411001C6406 /* UncompletedRowView.swift in Sources */,
4E40E0BE29F25ABB00B85369 /* SendAmount.swift in Sources */,
4E8E25332A1CD39700A27BFA /* EqualIconWidthDomain.swift in Sources */,
- 4EB095542989CBFE0043A8A1 /* PaymentURIModel.swift in Sources */,
+ 4EB095542989CBFE0043A8A1 /* Model+Payment.swift in Sources */,
4EB0954F2989CBFE0043A8A1 /* SettingsView.swift in Sources */,
4EB095552989CBFE0043A8A1 /* PaymentURIView.swift in Sources */,
4EB095612989CBFE0043A8A1 /* WithdrawURIView.swift in Sources */,
@@ -741,24 +754,27 @@
4EB0956B2989CBFE0043A8A1 /* TextFieldAlert.swift in Sources */,
4EBA82AD2A3F580500E5F39A /* QuiteSomeCoins.swift in Sources */,
4EB431672A1E55C700C5690E /* ManualWithdrawDone.swift in Sources */,
- 4E9320472A164BC700A87B0E /* ReceivePurpose.swift in Sources */,
+ 4E9320472A164BC700A87B0E /* PaymentPurpose.swift in Sources */,
4E753A082A0B6A5F002D9328 /* ShareSheet.swift in Sources */,
4EB0956C2989CBFE0043A8A1 /* AmountView.swift in Sources */,
+ 4E3B4BC32A42252300CC88B8 /* P2pAcceptDone.swift in Sources */,
4E363CBE2A23CB2100D7E98C /* AnyTransition+backslide.swift in Sources */,
- 4EB095592989CBFE0043A8A1 /* TransactionsModel.swift in Sources */,
+ 4EB095592989CBFE0043A8A1 /* Model+Transactions.swift in Sources */,
4EB0955F2989CBFE0043A8A1 /* WalletEmptyView.swift in Sources */,
4E16E12329F3BB99008B9C86 /* CurrencyFormatter.swift in Sources */,
- 4EB095192989CBB00043A8A1 /* WalletInitModel.swift in Sources */,
4EB095092989CB7C0043A8A1 /* TalerDater.swift in Sources */,
+ 4E3B4BC52A428AF700CC88B8 /* Model+Balances.swift in Sources */,
4E363CC22A2621C200D7E98C /* LocalizedAlertError.swift in Sources */,
4EB0950E2989CB9A0043A8A1 /* quickjs.swift in Sources */,
4E53A33729F50B7B00830EC2 /* CurrencyField.swift in Sources */,
- 4EB095152989CBB00043A8A1 /* SettingsModel.swift in Sources */,
+ 4EB095152989CBB00043A8A1 /* Model+Settings.swift in Sources */,
4EB095692989CBFE0043A8A1 /* ErrorView.swift in Sources */,
- 4EB0956E2989CBFE0043A8A1 /* PendingModel.swift in Sources */,
+ 4E3B4BC72A429F2A00CC88B8 /* View+Notification.swift in Sources */,
+ 4EB0956E2989CBFE0043A8A1 /* Model+Pending.swift in Sources */,
4EB095522989CBFE0043A8A1 /* ExchangeListView.swift in Sources */,
4EB095642989CBFE0043A8A1 /* WithdrawProgressView.swift in Sources */,
4EEC157A29F9427F00D46A03 /* QRSheet.swift in Sources */,
+ 4E3B4BC12A41E6C200CC88B8 /* P2pReceiveURIView.swift in Sources */,
4E6EDD872A363D8D0031D520 /* ListStyle.swift in Sources */,
4EB095582989CBFE0043A8A1 /* TransactionDetailView.swift in Sources */,
4EB095202989CBCB0043A8A1 /* WalletCore.swift in Sources */,
@@ -769,13 +785,13 @@
4EB095162989CBB00043A8A1 /* WalletModel.swift in Sources */,
4EB0955A2989CBFE0043A8A1 /* URLSheet.swift in Sources */,
4ED2F94B2A278F5100453B40 /* ThreeAmounts.swift in Sources */,
- 4EB095622989CBFE0043A8A1 /* WithdrawModel.swift in Sources */,
+ 4EB095622989CBFE0043A8A1 /* Model+Withdraw.swift in Sources */,
4EC90C782A1B528B0071DC58 /* ExchangeSectionView.swift in Sources */,
4E7940DE29FC307C00A9AEA1 /* SendPurpose.swift in Sources */,
- 4ECB62802A0BA6DF004ABBB7 /* Peer2peerModel.swift in Sources */,
+ 4ECB62802A0BA6DF004ABBB7 /* Model+P2P.swift in Sources */,
4EB0950A2989CB7C0043A8A1 /* TalerStrings.swift in Sources */,
4EA551252A2C923600FEC9A8 /* CurrencyInputView.swift in Sources */,
- 4E363CBC2A237E0900D7E98C /* URL+iban.swift in Sources */,
+ 4E363CBC2A237E0900D7E98C /* URL+id+iban.swift in Sources */,
4E9320452A1645B600A87B0E /* RequestPayment.swift in Sources */,
4EB095502989CBFE0043A8A1 /* SettingsItem.swift in Sources */,
4EB0955C2989CBFE0043A8A1 /* BalanceRowView.swift in Sources */,
diff --git a/TalerWallet1/Backend/Transaction.swift b/TalerWallet1/Backend/Transaction.swift
@@ -102,8 +102,8 @@ struct TransactionCommon: Decodable {
case payment
case refund
case refresh
- case reward
-// case tip
+ case reward // get paid for e.g. survey participation
+// case tip // tip personnel at restaurants
case peerPushDebit = "peer-push-debit" // send coins to peer, show QR
case scanPushCredit = "peer-push-credit" // scan QR, receive coins from peer
case peerPullCredit = "peer-pull-credit" // request payment from peer, show QR
@@ -329,13 +329,13 @@ enum Transaction: Decodable, Hashable, Identifiable {
case .refund: return String(localized: "Refund")
case .refresh: return String(localized: "Refresh")
case .reward: return String(localized: "Reward")
- case .peerPushDebit: return String(localized: "P2P Send")
- case .scanPushCredit: return String(localized: "P2P Receive")
- case .peerPullCredit: return String(localized: "P2P Invoice")
- case .scanPullDebit: return String(localized: "P2P Payment")
+ case .peerPushDebit: return String(localized: "P2P Send", comment: "send coins to another wallet")
+ case .scanPushCredit: return String(localized: "P2P Receive", comment: "scan to receive coins sent from another wallet")
+ case .peerPullCredit: return String(localized: "P2P Invoice", comment: "send invoice to another wallet")
+ case .scanPullDebit: return String(localized: "P2P Payment", comment: "scan invoice to pay to another wallet")
}
}
-
+
static func == (lhs: Transaction, rhs: Transaction) -> Bool {
return lhs.id == rhs.id
}
@@ -360,13 +360,16 @@ enum Transaction: Decodable, Hashable, Identifiable {
var isP2pOutgoing: Bool { isSendCoins || isPayInvoice}
var isP2pIncoming: Bool { isSendInvoice || isRcvCoins}
- var isDone : Bool { common.txState.major == .done }
var isPending : Bool { common.txState.major == .pending }
+ var isDone : Bool { common.txState.major == .done }
var isAborting : Bool { common.txState.major == .aborting }
var isAborted : Bool { common.txState.major == .aborted }
+ var isSuspended : Bool { common.txState.major == .suspended }
+ var isDialog : Bool { common.txState.major == .dialog }
+ var isAbSuspended: Bool { common.txState.major == .suspendedAborting }
var isFailed : Bool { common.txState.major == .failed }
var isExpired : Bool { common.txState.major == .expired }
- var isSpecial : Bool { isPending || isAborting || isAborted || isFailed || isExpired }
+ var isSpecial : Bool { !(isDone || isPending) }
var isAbortable : Bool { common.txActions.contains(.abort) }
var isFailable : Bool { common.txActions.contains(.fail) }
diff --git a/TalerWallet1/Backend/WalletCore.swift b/TalerWallet1/Backend/WalletCore.swift
@@ -6,6 +6,7 @@ import SwiftUI // FOUNDATION has no AppStorage
import AnyCodable
import FTalerWalletcore
import SymLog
+import os
/// Delegate for the wallet backend.
protocol WalletBackendDelegate {
@@ -22,13 +23,14 @@ class WalletCore: QuickjsMessageHandler {
private var queue: DispatchQueue
private var semaphore: DispatchSemaphore
- private var quickjs: Quickjs
+ private let quickjs: Quickjs
private var requestsMade: UInt // counter for array of completion closures
private var completions: [UInt : (Date, (UInt, Date, Data?, WalletBackendResponseError?) -> Void)] = [:]
var delegate: WalletBackendDelegate?
var versionInfo: VersionInfo? // shown in SettingsView
var developDelay: Bool? // if set in SettingsView will delay wallet-core after each action
+ let logger = Logger (subsystem: "net.taler.gnu", category: "wallet-core")
private struct FullRequest: Encodable {
let operation: String
@@ -164,6 +166,9 @@ extension WalletCore {
} else if id.hasPrefix("withdraw:") {
// TODO: handle withdraw
// symLog.log("\(pendingOp): \(id)")
+ } else if id.hasPrefix("peer-pull-credit:") {
+ // TODO: handle peer-pull-credit
+// symLog.log("\(pendingOp): \(id)")
} else if id.hasPrefix("peer-push-debit:") {
// TODO: handle peer-push-debit
// symLog.log("\(pendingOp): \(id)")
@@ -176,8 +181,10 @@ print("\n❗️ \(pendingOp): \(id)\n") // this is a new pendingOp I have
private func handleStateTransition(_ jsonData: Data) throws {
do {
let decoded = try JSONDecoder().decode(TransactionTransition.self, from: jsonData)
- postNotification(.TransactionStateTransition,
- userInfo: [TRANSACTIONTRANSITION: decoded])
+ if decoded.newTxState != decoded.oldTxState {
+ postNotification(.TransactionStateTransition,
+ userInfo: [TRANSACTIONTRANSITION: decoded])
+ }
} catch { // rethrows
symLog.log(jsonData) // TODO: .error
throw WalletBackendError.deserializationError
@@ -315,7 +322,7 @@ extension WalletCore {
sendRequest(request: reqData) { requestId, timeSent, result, error in
let timeUsed = Date.now - timeSent
let millisecs = timeUsed.milliseconds
-print("Request \(requestId) took \(millisecs) ms")
+ self.logger.log("Request \(requestId) took \(millisecs) ms")
var err: Error? = nil
if let json = result {
do {
@@ -339,7 +346,7 @@ print("Request \(requestId) took \(millisecs) ms")
} else {
self.symLog.log(json) // TODO: .error
}
- err = error // this will be thrown, otherwise keep nil
+ err = error // this will be thrown in continuation.resume(throwing:), otherwise keep nil
}
} else {
if let error = error {
diff --git a/TalerWallet1/Controllers/Controller.swift b/TalerWallet1/Controllers/Controller.swift
@@ -14,10 +14,12 @@ enum BackendState {
case error
}
-enum UrlCommand {
+enum UrlCommand: String {
case unknown
case withdraw
case pay
+ case payPull = "pay-pull"
+ case payPush = "pay-push"
}
// MARK: -
@@ -33,13 +35,12 @@ class Controller: ObservableObject {
backendState = .instantiated
}
- @MainActor func initWalletCoreM()
+ @MainActor func initWalletCoreM(_ model: WalletModel)
async throws { // M for MainActor
if backendState == .instantiated {
backendState = .initing
do {
- let walletInitModel = WalletInitModel()
- let versionInfo = try await walletInitModel.initWalletT()
+ let versionInfo = try await model.initWalletCoreT()
WalletCore.shared.versionInfo = versionInfo
backendState = .ready // dismiss the launch animation
} catch { // rethrows
@@ -48,7 +49,7 @@ class Controller: ObservableObject {
throw error
}
} else {
- symLog.log("Yikes❗️ wallet-core already initialized") // TODO: .warning
+ symLog.log("Yikes❗️ wallet-core already initialized") // TODO: .fault
}
}
}
@@ -93,6 +94,10 @@ extension Controller {
return UrlCommand.withdraw
case "pay":
return UrlCommand.pay
+ case "pay-pull":
+ return UrlCommand.payPull
+ case "pay-push":
+ return UrlCommand.payPush
default:
symLog.log("unknown command taler://\(command)")
}
diff --git a/TalerWallet1/Controllers/DebugViewC.swift b/TalerWallet1/Controllers/DebugViewC.swift
@@ -84,6 +84,8 @@ public let SHEET_PAY_P2P = SHEET_RCV_REWARD + 10 // 160 Pay P
// MARK: P2P Receive Coins
// p2p push credit - openURL (Link or scan QR)
public let SHEET_RCV_P2P = SHEET_PAY_P2P + 10 // 170 Receive P2P Coins
+public let SHEET_RCV_P2P_TOS = SHEET_RCV_P2P + 1 // 171 Receive P2P TOSView
+public let SHEET_RCV_P2P_ACCEPT = SHEET_RCV_P2P_TOS + 1 // 172 Receive P2P AcceptView
//public let SHEET_REFUND =
diff --git a/TalerWallet1/Controllers/TalerWallet1App.swift b/TalerWallet1/Controllers/TalerWallet1App.swift
@@ -22,6 +22,7 @@ struct TalerWallet1App: App {
private let walletCore = WalletCore.shared
// our main controller
private let controller = Controller.shared
+ private let model = WalletModel.shared
private let debugViewC = DebugViewC.shared
func scheduleAppRefresh() {
@@ -36,12 +37,13 @@ struct TalerWallet1App: App {
.environmentObject(debugViewC) // change viewID / sheetID
.environmentObject(viewState) // popToRoot
.environmentObject(controller)
+ .environmentObject(model)
/// external events are taler:// or payto:// URLs passed to this app
/// we handle them in .onOpenURL in MainView.swift
.handlesExternalEvents(preferring: ["*"], allowing: ["*"])
.task {
symLog.log("task -> initWalletCore")
- try! await controller.initWalletCoreM() // will (and should) crash on failure
+ try! await controller.initWalletCoreM(model) // will (and should) crash on failure
symLog.log("task -> initWalletCore done")
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification, object: nil)) { _ in
diff --git a/TalerWallet1/Helper/URL+iban.swift b/TalerWallet1/Helper/URL+id+iban.swift
diff --git a/TalerWallet1/Helper/View+Notification.swift b/TalerWallet1/Helper/View+Notification.swift
@@ -0,0 +1,75 @@
+// MIT License
+// Copyright © John Sundell
+//
+// 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 SwiftUI
+
+extension View {
+ func onNotification(
+ _ notificationName: Notification.Name,
+ perform action: @escaping () -> Void
+ ) -> some View {
+ onReceive(NotificationCenter.default
+ .publisher(for: notificationName)
+ ) { _ in
+ action()
+ }
+ }
+ func onNotification(
+ _ notificationName: Notification.Name,
+ perform action: @escaping (_ notification: Notification) -> Void
+ ) -> some View {
+ onReceive(NotificationCenter.default
+ .publisher(for: notificationName)
+ ) { notification in
+ action(notification)
+ }
+ }
+
+ func onNotificationM( // M for main thread
+ _ notificationName: Notification.Name,
+ perform action: @escaping () -> Void
+ ) -> some View {
+ onReceive(NotificationCenter.default
+ .publisher(for: notificationName)
+ .receive(on: RunLoop.main)
+ ) { _ in
+ action()
+ }
+ }
+
+ func onNotificationM( // M for main thread
+ _ notificationName: Notification.Name,
+ perform action: @escaping (_ notification: Notification) -> Void
+ ) -> some View {
+ onReceive(NotificationCenter.default
+ .publisher(for: notificationName)
+ .receive(on: RunLoop.main)
+ ) { notification in
+ action(notification)
+ }
+ }
+
+ func onAppEnteredBackground(
+ perform action: @escaping () -> Void
+ ) -> some View {
+ onNotification(
+ UIApplication.didEnterBackgroundNotification,
+ perform: action
+ )
+ }
+}
diff --git a/TalerWallet1/Helper/View+dismissTop.swift b/TalerWallet1/Helper/View+dismissTop.swift
@@ -18,63 +18,6 @@
//
import SwiftUI
-// John Sundell
-extension View {
- func onNotification(
- _ notificationName: Notification.Name,
- perform action: @escaping () -> Void
- ) -> some View {
- onReceive(NotificationCenter.default
- .publisher(for: notificationName)
- ) { _ in
- action()
- }
- }
- func onNotification(
- _ notificationName: Notification.Name,
- perform action: @escaping (_ notification: Notification) -> Void
- ) -> some View {
- onReceive(NotificationCenter.default
- .publisher(for: notificationName)
- ) { notification in
- action(notification)
- }
- }
-
- func onNotificationM( // M for main thread
- _ notificationName: Notification.Name,
- perform action: @escaping () -> Void
- ) -> some View {
- onReceive(NotificationCenter.default
- .publisher(for: notificationName)
- .receive(on: RunLoop.main)
- ) { _ in
- action()
- }
- }
-
- func onNotificationM( // M for main thread
- _ notificationName: Notification.Name,
- perform action: @escaping (_ notification: Notification) -> Void
- ) -> some View {
- onReceive(NotificationCenter.default
- .publisher(for: notificationName)
- .receive(on: RunLoop.main)
- ) { notification in
- action(notification)
- }
- }
-
- func onAppEnteredBackground(
- perform action: @escaping () -> Void
- ) -> some View {
- onNotification(
- UIApplication.didEnterBackgroundNotification,
- perform: action
- )
- }
-}
-
/// This is just a workaround for a SwiftUI bug
/// A presented sheet (SwiftUI view) doesn't always close when calling "dismiss()" provided by @Environment(\.dismiss),
/// so we are walking the view stack to find the top presentedViewController (UIKit) and dismiss it.
diff --git a/TalerWallet1/Model/BalancesModel.swift b/TalerWallet1/Model/BalancesModel.swift
@@ -1,85 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-// MARK: -
-class BalancesModel: WalletModel {
-
- override init(_ symbol: Int = -1) { // init with 0 to disable logging for this class
- super.init(0)//symbol)
- }
-}
-// MARK: -
-/// A request to get the balances held in the wallet.
-fileprivate struct GetBalances: WalletBackendFormattedRequest {
- func operation() -> String { return "getBalances" }
- func args() -> Args { return Args() }
-
- struct Args: Encodable {} // no arguments needed
-
- struct Response: Decodable { // list of balances
- var balances: [Balance]
- }
-}
-// MARK: -
-/// A currency balance
-struct Balance: Decodable, Hashable {
- var available: Amount
- var pendingIncoming: Amount
- var pendingOutgoing: Amount
- var hasPendingTransactions: Bool
- var requiresUserInput: Bool
- var scopeInfo: ScopeInfo
-
- public static func == (lhs: Balance, rhs: Balance) -> Bool {
- return lhs.available == rhs.available &&
- lhs.pendingIncoming == rhs.pendingIncoming &&
- lhs.pendingOutgoing == rhs.pendingOutgoing &&
- lhs.hasPendingTransactions == rhs.hasPendingTransactions &&
- lhs.requiresUserInput == rhs.requiresUserInput
- }
-
- public func hash(into hasher: inout Hasher) {
- hasher.combine(available)
- hasher.combine(pendingIncoming)
- hasher.combine(pendingOutgoing)
- hasher.combine(hasPendingTransactions)
- hasher.combine(requiresUserInput)
- }
-}
-// MARK: -
-extension BalancesModel {
- /// fetch Balances from Wallet-Core. No networking involved
- @MainActor func fetchBalancesM()
- async -> [Balance] { // M for MainActor
- do {
- let request = GetBalances()
- let response = try await sendRequest(request, ASYNCDELAY)
- return response.balances // trigger view update in BalancesListView
- } catch {
- return []
- }
- }
-}
-
-// MARK: -
-extension BalancesModel {
- private static var currencies: [String] = [] // names of currencies
- private static var models: [BalancesModel] = [] // one model per currency
-
- static func model(currency: String) -> BalancesModel {
- if let index = BalancesModel.currencies.firstIndex(of:currency) {
- let model = BalancesModel.models[index]
- return model
- } else { // new currency
- let model = BalancesModel()
- BalancesModel.models.append(model)
- BalancesModel.currencies.append(currency)
- return model
- }
- }
-}
diff --git a/TalerWallet1/Model/ExchangeModel.swift b/TalerWallet1/Model/ExchangeModel.swift
@@ -1,121 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-import SymLog
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-class ExchangeModel: WalletModel {
- override init(_ symbol: Int = -1) {
- super.init(0)//symbol)
- }
-}
-// MARK: -
-/// The result from wallet-core's ListExchanges
-struct Exchange: Codable, Hashable, Identifiable {
- static func == (lhs: Exchange, rhs: Exchange) -> Bool {
- return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
- lhs.exchangeStatus == rhs.exchangeStatus &&
- lhs.permanent == rhs.permanent
- }
-
- var exchangeBaseUrl: String
- var currency: String?
- var paytoUris: [String]
- var tosStatus: String
- var exchangeStatus: String
- var ageRestrictionOptions: [Int]
- var permanent: Bool
- var lastUpdateErrorInfo: ExchangeError?
-
- var id: String {
- exchangeBaseUrl
- }
- var name: String? {
- if let url = URL(string: exchangeBaseUrl) {
- if let host = url.host {
- return host
- }
- }
- return nil
- }
-}
-
-struct ExchangeError: Codable, Hashable {
- var error: HTTPError
-}
-
-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: -
-/// A request to list exchanges.
-fileprivate struct ListExchanges: WalletBackendFormattedRequest {
- func operation() -> String { return "listExchanges" }
- func args() -> Args { return Args() }
-
- struct Args: Encodable {} // no arguments needed
-
- struct Response: Decodable { // list of known exchanges
- var exchanges: [Exchange]
- }
-}
-
-/// A request to add an exchange.
-fileprivate struct AddExchange: WalletBackendFormattedRequest {
- func operation() -> String { return "addExchange" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
-
- var exchangeBaseUrl: String
-
- struct Args: Encodable {
- var exchangeBaseUrl: String
- }
-
- struct Response: Decodable {}
-}
-// MARK: -
-extension ExchangeModel {
- /// ask wallet-core for its list of known exchanges
- @MainActor func listExchangesM()
- async -> [Exchange] { // M for MainActor
- do {
- let request = ListExchanges()
- let response = try await sendRequest(request, ASYNCDELAY)
- return response.exchanges
- } catch {
- return [] // empty, but not nil
- }
- }
-
- /// add a new exchange with URL to the wallet's list of known exchanges
- func addExchange(url: String) async throws {
- symLog?.log("adding exchange: \(url)") // TODO: .notice
- let request = AddExchange(exchangeBaseUrl: url)
- _ = try await sendRequest(request)
- }
-}
-
-// MARK: -
-extension ExchangeModel {
- private static var models: [ExchangeModel] = [] // a list of models even though I currently need only one
-
- static func model() -> ExchangeModel {
- if ExchangeModel.models.count > 0 {
- let model = ExchangeModel.models[0]
- return model
- } else { // new model
- let model = ExchangeModel()
- ExchangeModel.models.append(model)
- return model
- }
- }
-}
diff --git a/TalerWallet1/Model/Model+Balances.swift b/TalerWallet1/Model/Model+Balances.swift
@@ -0,0 +1,58 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
+
+// MARK: -
+/// A currency balance
+struct Balance: Decodable, Hashable {
+ var available: Amount
+ var scopeInfo: ScopeInfo
+ var requiresUserInput: Bool
+ var hasPendingTransactions: Bool
+
+ public static func == (lhs: Balance, rhs: Balance) -> Bool {
+ return lhs.available == rhs.available &&
+ lhs.scopeInfo == rhs.scopeInfo &&
+ lhs.requiresUserInput == rhs.requiresUserInput &&
+ lhs.hasPendingTransactions == rhs.hasPendingTransactions
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(available)
+ hasher.combine(scopeInfo)
+ hasher.combine(requiresUserInput)
+ hasher.combine(hasPendingTransactions)
+ }
+}
+// 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() }
+
+ struct Args: Encodable {} // no arguments needed
+
+ struct Response: Decodable { // list of balances
+ var balances: [Balance]
+ }
+}
+// MARK: -
+extension WalletModel {
+ /// fetch Balances from Wallet-Core. No networking involved
+ @MainActor func balancesM()
+ async -> [Balance] { // M for MainActor
+ do {
+ let request = Balances()
+ logger.info("balancesM")
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response.balances // trigger view update in BalancesListView
+ } catch {
+ logger.error("balancesM failed: \(error)")
+ return []
+ }
+ }
+}
diff --git a/TalerWallet1/Model/Model+Exchange.swift b/TalerWallet1/Model/Model+Exchange.swift
@@ -0,0 +1,104 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import SymLog
+fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
+
+// MARK: -
+/// The result from wallet-core's ListExchanges
+struct Exchange: Codable, Hashable, Identifiable {
+ static func == (lhs: Exchange, rhs: Exchange) -> Bool {
+ return lhs.exchangeBaseUrl == rhs.exchangeBaseUrl &&
+ lhs.exchangeStatus == rhs.exchangeStatus &&
+ lhs.permanent == rhs.permanent
+ }
+
+ var exchangeBaseUrl: String
+ var currency: String?
+ var paytoUris: [String]
+ var tosStatus: String
+ var exchangeStatus: String
+ var ageRestrictionOptions: [Int]
+ var permanent: Bool
+ var lastUpdateErrorInfo: ExchangeError?
+
+ var id: String {
+ exchangeBaseUrl
+ }
+ var name: String? {
+ if let url = URL(string: exchangeBaseUrl) {
+ if let host = url.host {
+ return host
+ }
+ }
+ return nil
+ }
+}
+
+struct ExchangeError: Codable, Hashable {
+ var error: HTTPError
+}
+
+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: -
+/// A request to list exchanges.
+fileprivate struct ListExchanges: WalletBackendFormattedRequest {
+ func operation() -> String { return "listExchanges" }
+ func args() -> Args { return Args() }
+
+ struct Args: Encodable {} // no arguments needed
+
+ struct Response: Decodable { // list of known exchanges
+ var exchanges: [Exchange]
+ }
+}
+
+/// A request to add an exchange.
+fileprivate struct AddExchange: WalletBackendFormattedRequest {
+ func operation() -> String { return "addExchange" }
+ func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl) }
+
+ var exchangeBaseUrl: String
+
+ struct Args: Encodable {
+ var exchangeBaseUrl: String
+ }
+
+ struct Response: Decodable {}
+}
+// MARK: -
+extension WalletModel {
+ /// ask wallet-core for its list of known exchanges
+ @MainActor func listExchangesM()
+ async -> [Exchange] { // M for MainActor
+ do {
+ let request = ListExchanges()
+ logger.info("ListExchanges")
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response.exchanges
+ } catch {
+ logger.error("listExchangesM failed: \(error)")
+ return [] // empty, but not nil
+ }
+ }
+
+ /// add a new exchange with URL to the wallet's list of known exchanges
+ func addExchange(url: String)
+ async throws {
+ symLog?.log("adding exchange: \(url)") // TODO: .notice
+ let request = AddExchange(exchangeBaseUrl: url)
+ logger.info("adding exchange: \(url)")
+ _ = try await sendRequest(request)
+ }
+}
diff --git a/TalerWallet1/Model/Model+P2P.swift b/TalerWallet1/Model/Model+P2P.swift
@@ -0,0 +1,168 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import AnyCodable
+//import SymLog
+fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
+
+// MARK: - PeerContractTerms
+struct PeerContractTerms: Codable {
+ let amount: Amount
+ let summary: String
+ let purse_expiration: Timestamp
+}
+// MARK: -
+/// The result from CheckPeerPushDebit
+struct CheckPeerPushDebitResponse: Codable {
+ let exchangeBaseUrl: String?
+ let amountRaw: Amount
+ let amountEffective: Amount
+ let maxExpirationDate: Timestamp? // TODO: limit expiration (30 days or 7 days)
+}
+/// A request to check fees before sending coins to another wallet.
+fileprivate struct CheckPeerPushDebit: WalletBackendFormattedRequest {
+ typealias Response = CheckPeerPushDebitResponse
+ func operation() -> String { return "checkPeerPushDebit" }
+ func args() -> Args { return Args(amount: amount) }
+
+ var amount: Amount
+ struct Args: Encodable {
+ var amount: Amount
+ }
+}
+// MARK: -
+/// The result from CheckPeerPullCredit
+struct CheckPeerPullCreditResponse: Codable {
+ let scopeInfo: ScopeInfo?
+ let exchangeBaseUrl: String?
+ let amountRaw: Amount
+ let amountEffective: Amount
+ var numCoins: Int? // Number of coins this amountEffective will create
+}
+/// A request to check fees before invoicing another wallet.
+fileprivate struct CheckPeerPullCredit: WalletBackendFormattedRequest {
+ typealias Response = CheckPeerPullCreditResponse
+ func operation() -> String { return "checkPeerPullCredit" }
+ func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, scopeInfo: scopeInfo, amount: amount) }
+
+ var exchangeBaseUrl: String?
+ var scopeInfo: ScopeInfo?
+ var amount: Amount
+ struct Args: Encodable {
+ var exchangeBaseUrl: String?
+ var scopeInfo: ScopeInfo?
+ var amount: Amount
+ }
+}
+// MARK: -
+/// The result from InitiatePeerPushDebit
+struct PeerPushResponse: Codable {
+ let contractPriv: String
+ let mergePriv: String
+ let pursePub: String
+ let exchangeBaseUrl: String
+ let talerUri: String
+ let transactionId: String
+}
+/// A request to send coins to another wallet.
+fileprivate struct InitiatePeerPushDebit: WalletBackendFormattedRequest {
+ typealias Response = PeerPushResponse
+ func operation() -> String { return "initiatePeerPushDebit" }
+ func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
+ partialContractTerms: partialContractTerms) }
+
+ var exchangeBaseUrl: String?
+ var partialContractTerms: PeerContractTerms
+ struct Args: Encodable {
+ var exchangeBaseUrl: String?
+ var partialContractTerms: PeerContractTerms
+ }
+}
+
+struct PreparePeerPushCreditResponse: Codable {
+ let contractTerms: PeerContractTerms
+ let amountRaw: Amount
+ let amountEffective: Amount
+ let peerPushPaymentIncomingId: String
+}
+/// A request to receive coins from another wallet.
+fileprivate struct PreparePeerPushCredit: WalletBackendFormattedRequest {
+ typealias Response = PreparePeerPushCreditResponse
+ func operation() -> String { return "preparePeerPushCredit" }
+ func args() -> Args { return Args(talerUri: talerUri) }
+
+ var talerUri: String
+ struct Args: Encodable {
+ var talerUri: String
+ }
+}
+// MARK: -
+/// The result from InitiatePeerPullCredit
+struct PeerPullResponse: Codable {
+ let talerUri: String
+ let transactionId: String
+}
+/// A request to send a payment request to another wallet.
+fileprivate struct InitiatePeerPullCredit: WalletBackendFormattedRequest {
+ typealias Response = PeerPullResponse
+ func operation() -> String { return "initiatePeerPullCredit" }
+ func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
+ partialContractTerms: partialContractTerms) }
+
+ var exchangeBaseUrl: String?
+ var partialContractTerms: PeerContractTerms
+ struct Args: Encodable {
+ var exchangeBaseUrl: String?
+ var partialContractTerms: PeerContractTerms
+ }
+}
+// MARK: -
+extension WalletModel {
+ /// query exchange for fees (sending coins). Networking involved
+ @MainActor
+ func checkPeerPushDebitM(_ amount: Amount) // M for MainActor
+ async throws -> CheckPeerPushDebitResponse {
+ let request = CheckPeerPushDebit(amount: amount)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ /// generate peer-push. Networking involved
+ @MainActor
+ func initiatePeerPushDebitM(_ baseURL: String?, terms: PeerContractTerms) // M for MainActor
+ async throws -> PeerPushResponse {
+ let request = InitiatePeerPushDebit(exchangeBaseUrl: baseURL,
+ partialContractTerms: terms)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+
+ /// query exchange for fees (invoice coins). Networking involved
+ @MainActor
+ func checkPeerPullCreditM(_ amount: Amount, exchangeBaseUrl: String?) // M for MainActor
+ async throws -> CheckPeerPullCreditResponse {
+ let request = CheckPeerPullCredit(exchangeBaseUrl: exchangeBaseUrl, amount: amount)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ /// generate peer-pull. Networking involved
+ @MainActor
+ func initiatePeerPullCreditM(_ baseURL: String?, terms: PeerContractTerms) // M for MainActor
+ async throws -> PeerPullResponse {
+ let request = InitiatePeerPullCredit(exchangeBaseUrl: baseURL,
+ partialContractTerms: terms)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+
+ /// prepare peer-pull. Networking involved
+ @MainActor
+ func preparePeerPushCreditM(_ talerUri: String) // M for MainActor
+ async throws -> PreparePeerPushCreditResponse {
+ let request = PreparePeerPushCredit(talerUri: talerUri)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+}
diff --git a/TalerWallet1/Model/Model+Payment.swift b/TalerWallet1/Model/Model+Payment.swift
@@ -0,0 +1,143 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import AnyCodable
+//import SymLog
+fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
+
+// MARK: - ContractTerms
+struct ContractTerms: Codable {
+ let amount: Amount
+ let maxFee: Amount
+ let maxWireFee: Amount
+ let merchant: Merchant
+ let extra: Extra
+ let summary: String
+ let timestamp: Timestamp
+ let payDeadline: Timestamp
+ let refundDeadline: Timestamp
+ let wireTransferDeadline: Timestamp
+ let merchantBaseURL: String
+ let fulfillmentURL: String
+ let publicReorderURL: String
+ let auditors: [Auditor]
+ let exchanges: [ExchangeForPay]
+ let orderID, nonce, merchantPub: String
+ let products: [Product]
+ let hWire: String
+ let wireMethod: String
+ let wireFeeAmortization: Int
+
+ enum CodingKeys: String, CodingKey {
+ case amount
+ case maxFee = "max_fee"
+ case maxWireFee = "max_wire_fee"
+ case merchant, extra, summary
+ case timestamp
+ case payDeadline = "pay_deadline"
+ case refundDeadline = "refund_deadline"
+ case wireTransferDeadline = "wire_transfer_deadline"
+ case merchantBaseURL = "merchant_base_url"
+ case fulfillmentURL = "fulfillment_url"
+ case publicReorderURL = "public_reorder_url"
+ case auditors, exchanges
+ case orderID = "order_id"
+ case nonce
+ case merchantPub = "merchant_pub"
+ case products
+ case hWire = "h_wire"
+ case wireMethod = "wire_method"
+ case wireFeeAmortization = "wire_fee_amortization"
+ }
+}
+// MARK: - Auditor
+struct Auditor: Codable {
+ let name: String
+ let auditorPub: String
+ let url: String
+
+ enum CodingKeys: String, CodingKey {
+ case name
+ case auditorPub = "auditor_pub"
+ case url
+ }
+}
+// MARK: - Exchange
+struct ExchangeForPay: Codable {
+ let url: String
+ let masterPub: String
+
+ enum CodingKeys: String, CodingKey {
+ case url
+ case masterPub = "master_pub"
+ }
+}
+// MARK: - Extra
+struct Extra: Codable {
+ let articleName: String
+
+ enum CodingKeys: String, CodingKey {
+ case articleName = "article_name"
+ }
+}
+// MARK: -
+/// The result from PreparePayForUri
+struct PaymentDetailsForUri: Codable {
+// let status: String
+ let amountRaw: Amount
+ let amountEffective: Amount
+ let noncePriv: String
+ let proposalId: String
+ let contractTerms: ContractTerms
+ let contractTermsHash: String
+}
+/// A request to get an exchange's payment contract terms.
+fileprivate struct PreparePayForUri: WalletBackendFormattedRequest {
+ typealias Response = PaymentDetailsForUri
+ func operation() -> String { return "preparePayForUri" }
+ func args() -> Args { return Args(talerPayUri: talerPayUri) }
+
+ var talerPayUri: String
+ struct Args: Encodable {
+ var talerPayUri: String
+ }
+}
+// MARK: -
+/// The result from confirmPayForUri
+struct ConfirmPayResult: Decodable {
+ var type: String
+ var contractTerms: ContractTerms
+ var transactionId: String
+}
+/// 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(proposalId: proposalId) }
+
+ var proposalId: String
+ struct Args: Encodable {
+ var proposalId: String
+ }
+}
+// MARK: -
+extension WalletModel {
+ /// load payment details. Networking involved
+ @MainActor
+ func preparePayForUriM(_ talerPayUri: String) // M for MainActor
+ async throws -> PaymentDetailsForUri {
+ let request = PreparePayForUri(talerPayUri: talerPayUri)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ @MainActor
+ func confirmPayM(_ proposalId: String) // M for MainActor
+ async throws -> ConfirmPayResult {
+ let request = confirmPayForUri(proposalId: proposalId)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+}
diff --git a/TalerWallet1/Model/Model+Pending.swift b/TalerWallet1/Model/Model+Pending.swift
@@ -0,0 +1,55 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import AnyCodable
+import taler_swift
+import SymLog
+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 args() -> Args { Args() }
+
+ struct Args: Encodable {}
+
+ struct Response: Decodable {
+ var pendingOperations: [PendingOperation]
+ }
+}
+// MARK: -
+struct PendingOperation: Codable, Hashable {
+ var type: String
+ var exchangeBaseUrl: String?
+ var id: String
+ var isLongpolling: Bool
+ var givesLifeness: Bool
+ var isDue: Bool
+ var timestampDue: Timestamp
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(type)
+ hasher.combine(exchangeBaseUrl)
+ hasher.combine(id)
+ hasher.combine(isLongpolling)
+ hasher.combine(givesLifeness)
+ hasher.combine(isDue)
+ hasher.combine(timestampDue)
+ }
+}
+// MARK: -
+extension WalletModel {
+ @MainActor func getPendingOperationsM()
+ async -> [PendingOperation] { // M for MainActor
+ do {
+ let request = GetPendingOperations()
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response.pendingOperations
+ } catch {
+ return []
+ }
+ }
+}
diff --git a/TalerWallet1/Model/Model+Settings.swift b/TalerWallet1/Model/Model+Settings.swift
@@ -0,0 +1,102 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import SymLog
+fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
+
+fileprivate let DEMO_EXCHANGEBASEURL = DEMOEXCHANGE
+fileprivate let DEMO_BANKBASEURL = DEMOBANK
+fileprivate let DEMO_BANKAPIBASEURL = DEMOBANK + "/demobanks/default/access-api/"
+fileprivate let DEMO_MERCHANTBASEURL = DEMOBACKEND
+fileprivate let DEMO_MERCHANTAUTHTOKEN = "secret-token:sandbox"
+
+// MARK: -
+extension WalletModel {
+ @MainActor func loadTestKudosM()
+ async throws { // M for MainActor
+ let amount = Amount(currency: DEMOCURRENCY, integer: 11, fraction: 0)
+ let request = WalletBackendWithdrawTestBalance(amount: amount,
+ bankBaseUrl: DEMO_BANKBASEURL,
+ exchangeBaseUrl: DEMO_EXCHANGEBASEURL,
+ bankAccessApiBaseUrl: DEMO_BANKAPIBASEURL)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ symLog?.log("received: \(response)")
+ }
+
+ @MainActor func runIntegrationTestM(newVersion: Bool)
+ async throws { // M for MainActor
+ let amountW = Amount(currency: DEMOCURRENCY, integer: 3, fraction: 0)
+ let amountS = Amount(currency: DEMOCURRENCY, integer: 1, fraction: 0)
+ let request = WalletBackendRunIntegration(newVersion: newVersion,
+ amountToWithdraw: amountW,
+ amountToSpend: amountS,
+ bankBaseUrl: DEMO_BANKAPIBASEURL,
+ bankAccessApiBaseUrl: DEMO_BANKAPIBASEURL,
+ exchangeBaseUrl: DEMO_EXCHANGEBASEURL,
+ merchantBaseUrl: DEMO_MERCHANTBASEURL,
+ merchantAuthToken: DEMO_MERCHANTAUTHTOKEN)
+ let _ = try await sendRequest(request, ASYNCDELAY)
+ symLog?.log("runIntegrationTest finished")
+ }
+}
+// MARK: -
+/// A request to add a test balance to the wallet.
+fileprivate struct WalletBackendWithdrawTestBalance: WalletBackendFormattedRequest {
+ typealias Response = String
+ func operation() -> String { return "withdrawTestBalance" }
+ func args() -> Args {
+ return Args(amount: amount, bankBaseUrl: bankBaseUrl,
+ exchangeBaseUrl: exchangeBaseUrl, bankAccessApiBaseUrl: bankAccessApiBaseUrl)
+ }
+
+ var amount: Amount
+ var bankBaseUrl: String
+ var exchangeBaseUrl: String
+ var bankAccessApiBaseUrl: String
+
+ struct Args: Encodable {
+ var amount: Amount
+ var bankBaseUrl: String
+ var exchangeBaseUrl: String
+ var bankAccessApiBaseUrl: String
+ }
+}
+// MARK: -
+/// A request to add a test balance to the wallet.
+fileprivate struct WalletBackendRunIntegration: WalletBackendFormattedRequest {
+ struct Response: Decodable {}
+ func operation() -> String { return newVersion ? "runIntegrationTestV2" : "runIntegrationTest" }
+ func args() -> Args {
+ return Args(amountToWithdraw: amountToWithdraw,
+ amountToSpend: amountToSpend,
+ bankBaseUrl: bankBaseUrl,
+ bankAccessApiBaseUrl: bankAccessApiBaseUrl,
+ exchangeBaseUrl: exchangeBaseUrl,
+ merchantBaseUrl: merchantBaseUrl,
+ merchantAuthToken: merchantAuthToken
+ )
+ }
+
+ let newVersion: Bool
+
+ var amountToWithdraw: Amount
+ var amountToSpend: Amount
+ var bankBaseUrl: String
+ var bankAccessApiBaseUrl: String
+ var exchangeBaseUrl: String
+ var merchantBaseUrl: String
+ var merchantAuthToken: String
+
+ struct Args: Encodable {
+ var amountToWithdraw: Amount
+ var amountToSpend: Amount
+ var bankBaseUrl: String
+ var bankAccessApiBaseUrl: String
+ var exchangeBaseUrl: String
+ var merchantBaseUrl: String
+ var merchantAuthToken: String
+ }
+}
diff --git a/TalerWallet1/Model/Model+Transactions.swift b/TalerWallet1/Model/Model+Transactions.swift
@@ -0,0 +1,120 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import SymLog
+fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
+
+// MARK: -
+extension WalletModel {
+ static func specialTransactions(_ transactions: [Transaction]) -> [Transaction] {
+ transactions.filter { transaction in
+ transaction.isSpecial
+ }
+ }
+
+ static func completedTransactions(_ transactions: [Transaction]) -> [Transaction] {
+ transactions.filter { transaction in
+ transaction.isDone
+ }
+ }
+ static func pendingTransactions(_ transactions: [Transaction]) -> [Transaction] {
+ transactions.filter { transaction in
+ transaction.isPending
+ }
+ }
+ static func uncompletedTransactions(_ transactions: [Transaction]) -> [Transaction] {
+ transactions.filter { transaction in
+ !transaction.isDone && !transaction.isPending
+ }
+ }
+}
+
+// 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) }
+
+ var currency: String?
+ var search: String?
+
+ struct Args: Encodable {
+ var currency: String?
+ var search: String?
+ }
+
+ struct Response: Decodable { // list of transactions
+ var transactions: [Transaction]
+ }
+}
+/// A request to abort a wallet transaction by ID.
+struct AbortTransaction: WalletBackendFormattedRequest {
+ func operation() -> String { return "abortTransaction" }
+ func args() -> Args { return Args(transactionId: transactionId) }
+
+ var transactionId: String
+
+ struct Args: Encodable {
+ var transactionId: String
+ }
+
+ struct Response: Decodable {}
+}
+/// A request to delete a wallet transaction by ID.
+struct DeleteTransaction: WalletBackendFormattedRequest {
+ struct Response: Decodable {}
+ func operation() -> String { return "deleteTransaction" }
+ func args() -> Args { return Args(transactionId: transactionId) }
+
+ var transactionId: String
+
+ struct Args: Encodable {
+ var transactionId: String
+ }
+}
+
+// MARK: -
+extension WalletModel {
+ /// ask wallet-core for its list of transactions filtered by searchString
+ func fetchTransactionsT(currency: String? = nil, searchString: String? = nil)
+ async -> [Transaction] { // might be called from a background thread itself
+ do {
+ let request = GetTransactions(currency: currency, search: searchString)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response.transactions
+ } catch {
+ return []
+ }
+ }
+ /// fetch transactions from Wallet-Core. No networking involved
+ @MainActor func fetchTransactionsM(currency: String? = nil, searchString: String? = nil)
+ async -> [Transaction] { // M for MainActor
+ await fetchTransactionsT(currency: currency, searchString: searchString)
+ }
+
+ func abortTransactionT(transactionId: String)
+ async throws { // might be called from a background thread itself
+ let request = AbortTransaction(transactionId: transactionId)
+ let _ = try await sendRequest(request, ASYNCDELAY)
+ }
+ /// delete the specified transaction from Wallet-Core. No networking involved
+ @MainActor func abortTransactionM(transactionId: String)
+ async throws { // M for MainActor
+ try await abortTransactionT(transactionId: transactionId) // call abortTransaction on main thread
+ }
+
+ func deleteTransactionT(transactionId: String)
+ async throws { // might be called from a background thread itself
+ let request = DeleteTransaction(transactionId: transactionId)
+ let _ = try await sendRequest(request, ASYNCDELAY)
+ }
+ /// delete the specified transaction from Wallet-Core. No networking involved
+ @MainActor func deleteTransactionM(transactionId: String)
+ async throws { // M for MainActor
+ try await deleteTransactionT(transactionId: transactionId) // call deleteTransaction on main thread
+ }
+}
diff --git a/TalerWallet1/Model/Model+Withdraw.swift b/TalerWallet1/Model/Model+Withdraw.swift
@@ -0,0 +1,188 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import Foundation
+import taler_swift
+import SymLog
+fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
+
+// MARK: -
+/// The result from getWithdrawalDetailsForUri
+struct WithdrawUriInfoResponse: Decodable {
+ var amount: Amount
+ 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) }
+
+ var talerWithdrawUri: String
+ struct Args: Encodable {
+ var talerWithdrawUri: String
+ }
+}
+// MARK: -
+/// The result from getWithdrawalDetailsForAmount
+struct ManualWithdrawalDetails: Decodable {
+ var tosAccepted: Bool // Did the user accept the current version of the exchange's terms of service?
+ var amountRaw: Amount // Amount that the user will transfer to the exchange
+ var amountEffective: Amount // Amount that will be added to the user's wallet balance
+ var paytoUris: [String] // Ways to pay the exchange
+ var ageRestrictionOptions: [Int]? // Array of ages
+ var numCoins: Int? // Number of coins this amountEffective will create
+}
+/// A request to get an exchange's withdrawal details.
+fileprivate struct GetWithdrawalDetailsForAmount: WalletBackendFormattedRequest {
+ typealias Response = ManualWithdrawalDetails
+ func operation() -> String { return "getWithdrawalDetailsForAmount" }
+ func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, amount: amount) }
+
+ var exchangeBaseUrl: String
+ var amount: Amount
+ struct Args: Encodable {
+ var exchangeBaseUrl: String
+ var amount: Amount
+ }
+}
+// MARK: -
+struct ExchangeTermsOfService: Decodable {
+ var content: String
+ var currentEtag: String
+ var acceptedEtag: String?
+}
+/// 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) }
+
+ var exchangeBaseUrl: String
+ struct Args: Encodable {
+ var exchangeBaseUrl: String
+ }
+}
+// MARK: -
+/// A request to mark an exchange's terms of service as accepted.
+fileprivate struct SetExchangeTOSAccepted: WalletBackendFormattedRequest {
+ struct Response: Decodable {}
+ func operation() -> String { return "setExchangeTosAccepted" }
+ func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, etag: etag) }
+
+ var exchangeBaseUrl: String
+ var etag: String
+
+ struct Args: Encodable {
+ var exchangeBaseUrl: String
+ var etag: String
+ }
+}
+// MARK: -
+struct AcceptWithdrawalResponse: Decodable {
+ var reservePub: String
+ var confirmTransferUrl: String?
+ var transactionId: String
+}
+/// 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) }
+
+ var talerWithdrawUri: String
+ var exchangeBaseUrl: String
+
+ struct Args: Encodable {
+ var talerWithdrawUri: String
+ var exchangeBaseUrl: String
+ }
+}
+// MARK: -
+struct AcceptManualWithdrawalResult: Decodable {
+ var reservePub: String
+ var exchangePaytoUris: [String]
+ var transactionId: String
+}
+/// 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) }
+
+ var exchangeBaseUrl: String
+ var amount: Amount
+ var restrictAge: Int?
+
+ struct Args: Encodable {
+ var exchangeBaseUrl: String
+ var amount: Amount
+ var restrictAge: Int?
+ }
+}
+// MARK: -
+extension WalletModel {
+ /// load withdrawal details. Networking involved
+ @MainActor
+ func loadWithdrawalDetailsForUriM(_ talerWithdrawUri: String) // M for MainActor
+ async throws -> WithdrawUriInfoResponse {
+ let request = GetWithdrawalDetailsForURI(talerWithdrawUri: talerWithdrawUri)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ @MainActor
+ func loadWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: Amount) // M for MainActor
+ async throws -> ManualWithdrawalDetails {
+ let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: exchangeBaseUrl,
+ amount: amount)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ @MainActor
+ func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String) // M for MainActor
+ async throws -> ExchangeTermsOfService {
+ let request = GetExchangeTermsOfService(exchangeBaseUrl: exchangeBaseUrl)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ @MainActor
+ func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String) // M for MainActor
+ async throws -> Decodable {
+ let request = SetExchangeTOSAccepted(exchangeBaseUrl: exchangeBaseUrl, etag: etag)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ @MainActor
+ func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: String) // M for MainActor
+ async throws -> AcceptWithdrawalResponse? {
+ let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+ @MainActor
+ func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: Amount, restrictAge: Int?) // M for MainActor
+ async throws -> AcceptManualWithdrawalResult? {
+ let request = AcceptManualWithdrawal(exchangeBaseUrl: exchangeBaseUrl, amount: amount, restrictAge: restrictAge)
+ let response = try await sendRequest(request, ASYNCDELAY)
+ return response
+ }
+}
diff --git a/TalerWallet1/Model/PaymentURIModel.swift b/TalerWallet1/Model/PaymentURIModel.swift
@@ -1,169 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-import AnyCodable
-//import SymLog
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-class PaymentURIModel: WalletModel {
-
- override init(_ symbol: Int = -1) { // init with 0 to disable logging for this class
- super.init(0)//symbol)
- }
-}
-// MARK: - ContractTerms
-struct ContractTerms: Codable {
- let amount: Amount
- let maxFee: Amount
- let maxWireFee: Amount
- let merchant: Merchant
- let extra: Extra
- let summary: String
- let timestamp: Timestamp
- let payDeadline: Timestamp
- let refundDeadline: Timestamp
- let wireTransferDeadline: Timestamp
- let merchantBaseURL: String
- let fulfillmentURL: String
- let publicReorderURL: String
- let auditors: [Auditor]
- let exchanges: [ExchangeForPay]
- let orderID, nonce, merchantPub: String
- let products: [Product]
- let hWire: String
- let wireMethod: String
- let wireFeeAmortization: Int
-
- enum CodingKeys: String, CodingKey {
- case amount
- case maxFee = "max_fee"
- case maxWireFee = "max_wire_fee"
- case merchant, extra, summary
- case timestamp
- case payDeadline = "pay_deadline"
- case refundDeadline = "refund_deadline"
- case wireTransferDeadline = "wire_transfer_deadline"
- case merchantBaseURL = "merchant_base_url"
- case fulfillmentURL = "fulfillment_url"
- case publicReorderURL = "public_reorder_url"
- case auditors, exchanges
- case orderID = "order_id"
- case nonce
- case merchantPub = "merchant_pub"
- case products
- case hWire = "h_wire"
- case wireMethod = "wire_method"
- case wireFeeAmortization = "wire_fee_amortization"
- }
-}
-
-// MARK: - Auditor
-struct Auditor: Codable {
- let name: String
- let auditorPub: String
- let url: String
-
- enum CodingKeys: String, CodingKey {
- case name
- case auditorPub = "auditor_pub"
- case url
- }
-}
-
-// MARK: - Exchange
-struct ExchangeForPay: Codable {
- let url: String
- let masterPub: String
-
- enum CodingKeys: String, CodingKey {
- case url
- case masterPub = "master_pub"
- }
-}
-
-// MARK: - Extra
-struct Extra: Codable {
- let articleName: String
-
- enum CodingKeys: String, CodingKey {
- case articleName = "article_name"
- }
-}
-
-// MARK: -
-/// The result from PreparePayForUri
-struct PaymentDetailsForUri: Codable {
-// let status: String
- let amountRaw: Amount
- let amountEffective: Amount
- let noncePriv: String
- let proposalId: String
- let contractTerms: ContractTerms
- let contractTermsHash: String
-}
-/// A request to get an exchange's payment contract terms.
-fileprivate struct PreparePayForUri: WalletBackendFormattedRequest {
- typealias Response = PaymentDetailsForUri
- func operation() -> String { return "preparePayForUri" }
- func args() -> Args { return Args(talerPayUri: talerPayUri) }
-
- var talerPayUri: String
- struct Args: Encodable {
- var talerPayUri: String
- }
-}
-// MARK: -
-/// The result from confirmPayForUri
-struct ConfirmPayResult: Decodable {
- var type: String
- var contractTerms: ContractTerms
- var transactionId: String
-}
-/// 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(proposalId: proposalId) }
-
- var proposalId: String
- struct Args: Encodable {
- var proposalId: String
- }
-}
-// MARK: -
-extension PaymentURIModel {
- /// load payment details. Networking involved
- @MainActor
- func preparePayForUriM(_ talerPayUri: String) // M for MainActor
- async throws -> PaymentDetailsForUri {
- let request = PreparePayForUri(talerPayUri: talerPayUri)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- @MainActor
- func confirmPayM(_ proposalId: String) // M for MainActor
- async throws -> ConfirmPayResult {
- let request = confirmPayForUri(proposalId: proposalId)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
-}
-
-// MARK: -
-extension PaymentURIModel {
- private static var models: [PaymentURIModel] = [] // a list of models even though I currently need only one
-
- static func model() -> PaymentURIModel {
- if PaymentURIModel.models.count > 0 {
- let model = PaymentURIModel.models[0]
- return model
- } else { // new model
- let model = PaymentURIModel()
- PaymentURIModel.models.append(model)
- return model
- }
- }
-}
diff --git a/TalerWallet1/Model/Peer2peerModel.swift b/TalerWallet1/Model/Peer2peerModel.swift
@@ -1,134 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-import AnyCodable
-//import SymLog
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-class Peer2peerModel: WalletModel {
- override init(_ symbol: Int = -1) { // init with 0 to disable logging for this class
- super.init(0)//symbol)
- }
-}
-// MARK: - PeerContractTerms
-struct PeerContractTerms: Codable {
- let amount: Amount
- let summary: String
- let purse_expiration: Timestamp
-}
-// MARK: -
-/// The result from CheckPeerPushDebit
-struct CheckPeerPushDebitResponse: Codable {
- let exchangeBaseUrl: String?
- let amountRaw: Amount
- let amountEffective: Amount
- let maxExpirationDate: Timestamp? // TODO: limit expiration (30 days or 7 days)
-}
-/// A request to check fees before sending coins to another wallet.
-fileprivate struct CheckPeerPushDebit: WalletBackendFormattedRequest {
- typealias Response = CheckPeerPushDebitResponse
- func operation() -> String { return "checkPeerPushDebit" }
- func args() -> Args { return Args(amount: amount) }
-
- var amount: Amount
- struct Args: Encodable {
- var amount: Amount
- }
-}
-// MARK: -
-/// The result from CheckPeerPullCredit
-struct CheckPeerPullCreditResponse: Codable {
- let scopeInfo: ScopeInfo?
- let exchangeBaseUrl: String?
- let amountRaw: Amount
- let amountEffective: Amount
- var numCoins: Int? // Number of coins this amountEffective will create
-}
-/// A request to check fees before invoicing another wallet.
-fileprivate struct CheckPeerPullCredit: WalletBackendFormattedRequest {
- typealias Response = CheckPeerPullCreditResponse
- func operation() -> String { return "checkPeerPullCredit" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, scopeInfo: scopeInfo, amount: amount) }
-
- var exchangeBaseUrl: String?
- var scopeInfo: ScopeInfo?
- var amount: Amount
- struct Args: Encodable {
- var exchangeBaseUrl: String?
- var scopeInfo: ScopeInfo?
- var amount: Amount
- }
-}
-// MARK: -
-/// The result from InitiatePeerPushDebit
-struct PeerPushResponse: Codable {
- let contractPriv: String
- let mergePriv: String
- let pursePub: String
- let exchangeBaseUrl: String
- let talerUri: String
- let transactionId: String
-}
-/// A request to send coins to another wallet.
-fileprivate struct InitiatePeerPushDebit: WalletBackendFormattedRequest {
- typealias Response = PeerPushResponse
- func operation() -> String { return "initiatePeerPushDebit" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl,
- partialContractTerms: partialContractTerms) }
-
- var exchangeBaseUrl: String?
- var partialContractTerms: PeerContractTerms
- struct Args: Encodable {
- var exchangeBaseUrl: String?
- var partialContractTerms: PeerContractTerms
- }
-}
-// MARK: -
-extension Peer2peerModel {
- /// query exchange for fees (sending coins). Networking involved
- @MainActor
- func checkPeerPushDebitM(_ amount: Amount) // M for MainActor
- async throws -> CheckPeerPushDebitResponse {
- let request = CheckPeerPushDebit(amount: amount)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- /// query exchange for fees (invoice coins). Networking involved
- @MainActor
- func checkPeerPullCreditM(_ amount: Amount, exchangeBaseUrl: String?) // M for MainActor
- async throws -> CheckPeerPullCreditResponse {
- let request = CheckPeerPullCredit(exchangeBaseUrl: exchangeBaseUrl, amount: amount)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- /// generate peer-push. Networking involved
- @MainActor
- func initiatePeerPushDebitM(_ baseURL: String?, terms: PeerContractTerms) // M for MainActor
- async throws -> PeerPushResponse {
- let request = InitiatePeerPushDebit(exchangeBaseUrl: baseURL,
- partialContractTerms: terms)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
-}
-
-// MARK: -
-extension Peer2peerModel {
- private static var exchanges: [String] = [] // names of exchanges
- private static var models: [Peer2peerModel] = [] // one model per exchange
-
- static func model(baseURL: String) -> Peer2peerModel {
- if let index = Peer2peerModel.exchanges.firstIndex(of:baseURL) {
- let model = Peer2peerModel.models[index]
- return model
- } else { // new model
- let model = Peer2peerModel()
- Peer2peerModel.models.append(model)
- Peer2peerModel.exchanges.append(baseURL)
- return model
- }
- }
-}
diff --git a/TalerWallet1/Model/PendingModel.swift b/TalerWallet1/Model/PendingModel.swift
@@ -1,78 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import AnyCodable
-import taler_swift
-import SymLog
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-class PendingModel: WalletModel {
-
- override init(_ symbol: Int = -1) {
- super.init(0)//symbol)
- }
-}
-// MARK: -
-/// A request to list the backend's currently pending operations.
-fileprivate struct GetPendingOperations: WalletBackendFormattedRequest {
- func operation() -> String { return "getPendingOperations" }
- func args() -> Args { Args() }
-
- struct Args: Encodable {}
-
- struct Response: Decodable {
- var pendingOperations: [PendingOperation]
- }
-}
-// MARK: -
-struct PendingOperation: Codable, Hashable {
- var type: String
- var exchangeBaseUrl: String?
- var id: String
- var isLongpolling: Bool
- var givesLifeness: Bool
- var isDue: Bool
- var timestampDue: Timestamp
-
- public func hash(into hasher: inout Hasher) {
- hasher.combine(type)
- hasher.combine(exchangeBaseUrl)
- hasher.combine(id)
- hasher.combine(isLongpolling)
- hasher.combine(givesLifeness)
- hasher.combine(isDue)
- hasher.combine(timestampDue)
- }
-
-}
-// MARK: -
-extension PendingModel {
- @MainActor func getPendingOperationsM()
- async -> [PendingOperation] { // M for MainActor
- do {
- let request = GetPendingOperations()
- let response = try await sendRequest(request, ASYNCDELAY)
- return response.pendingOperations
- } catch {
- return []
- }
- }
-}
-
-// MARK: -
-extension PendingModel {
- private static var models: [PendingModel] = [] // a list of models even though I currently need only one
-
- static func model() -> PendingModel {
- if PendingModel.models.count > 0 {
- let model = PendingModel.models[0]
- return model
- } else { // new model
- let model = PendingModel()
- PendingModel.models.append(model)
- return model
- }
- }
-}
diff --git a/TalerWallet1/Model/SettingsModel.swift b/TalerWallet1/Model/SettingsModel.swift
@@ -1,108 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-import SymLog
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-fileprivate let DEMO_EXCHANGEBASEURL = DEMOEXCHANGE
-fileprivate let DEMO_BANKBASEURL = DEMOBANK
-fileprivate let DEMO_BANKAPIBASEURL = DEMOBANK + "/demobanks/default/access-api/"
-fileprivate let DEMO_MERCHANTBASEURL = DEMOBACKEND
-fileprivate let DEMO_MERCHANTAUTHTOKEN = "secret-token:sandbox"
-
-// MARK: -
-class SettingsModel: WalletModel {
- override init(_ symbol: Int = -1) { // init with 0 to disable logging for this class
- super.init(0)//symbol)
- }
-}
-// MARK: -
-extension SettingsModel {
- @MainActor func loadTestKudosM()
- async throws { // M for MainActor
- let amount = Amount(currency: DEMOCURRENCY, integer: 11, fraction: 0)
- let request = WalletBackendWithdrawTestBalance(amount: amount,
- bankBaseUrl: DEMO_BANKBASEURL,
- exchangeBaseUrl: DEMO_EXCHANGEBASEURL,
- bankAccessApiBaseUrl: DEMO_BANKAPIBASEURL)
- let response = try await sendRequest(request, ASYNCDELAY)
- symLog?.log("received: \(response)")
- }
-
- @MainActor func runIntegrationTestM(newVersion: Bool)
- async throws { // M for MainActor
- let amountW = Amount(currency: DEMOCURRENCY, integer: 3, fraction: 0)
- let amountS = Amount(currency: DEMOCURRENCY, integer: 1, fraction: 0)
- let request = WalletBackendRunIntegration(newVersion: newVersion,
- amountToWithdraw: amountW,
- amountToSpend: amountS,
- bankBaseUrl: DEMO_BANKAPIBASEURL,
- bankAccessApiBaseUrl: DEMO_BANKAPIBASEURL,
- exchangeBaseUrl: DEMO_EXCHANGEBASEURL,
- merchantBaseUrl: DEMO_MERCHANTBASEURL,
- merchantAuthToken: DEMO_MERCHANTAUTHTOKEN)
- let _ = try await sendRequest(request, ASYNCDELAY)
- symLog?.log("runIntegrationTest finished")
- }
-}
-// MARK: -
-/// A request to add a test balance to the wallet.
-fileprivate struct WalletBackendWithdrawTestBalance: WalletBackendFormattedRequest {
- typealias Response = String
- func operation() -> String { return "withdrawTestBalance" }
- func args() -> Args {
- return Args(amount: amount, bankBaseUrl: bankBaseUrl,
- exchangeBaseUrl: exchangeBaseUrl, bankAccessApiBaseUrl: bankAccessApiBaseUrl)
- }
-
- var amount: Amount
- var bankBaseUrl: String
- var exchangeBaseUrl: String
- var bankAccessApiBaseUrl: String
-
- struct Args: Encodable {
- var amount: Amount
- var bankBaseUrl: String
- var exchangeBaseUrl: String
- var bankAccessApiBaseUrl: String
- }
-}
-// MARK: -
-/// A request to add a test balance to the wallet.
-fileprivate struct WalletBackendRunIntegration: WalletBackendFormattedRequest {
- struct Response: Decodable {}
- func operation() -> String { return newVersion ? "runIntegrationTestV2" : "runIntegrationTest" }
- func args() -> Args {
- return Args(amountToWithdraw: amountToWithdraw,
- amountToSpend: amountToSpend,
- bankBaseUrl: bankBaseUrl,
- bankAccessApiBaseUrl: bankAccessApiBaseUrl,
- exchangeBaseUrl: exchangeBaseUrl,
- merchantBaseUrl: merchantBaseUrl,
- merchantAuthToken: merchantAuthToken
- )
- }
-
- let newVersion: Bool
-
- var amountToWithdraw: Amount
- var amountToSpend: Amount
- var bankBaseUrl: String
- var bankAccessApiBaseUrl: String
- var exchangeBaseUrl: String
- var merchantBaseUrl: String
- var merchantAuthToken: String
-
- struct Args: Encodable {
- var amountToWithdraw: Amount
- var amountToSpend: Amount
- var bankBaseUrl: String
- var bankAccessApiBaseUrl: String
- var exchangeBaseUrl: String
- var merchantBaseUrl: String
- var merchantAuthToken: String
- }
-}
diff --git a/TalerWallet1/Model/TransactionsModel.swift b/TalerWallet1/Model/TransactionsModel.swift
@@ -1,143 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-import SymLog
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-// MARK: -
-class TransactionsModel: WalletModel {
-
- static func specialTransactions(_ transactions: [Transaction]) -> [Transaction] {
- transactions.filter { transaction in
- transaction.isSpecial
- }
- }
-
- static func completedTransactions(_ transactions: [Transaction]) -> [Transaction] {
- transactions.filter { transaction in
- transaction.isDone
- }
- }
- static func pendingTransactions(_ transactions: [Transaction]) -> [Transaction] {
- transactions.filter { transaction in
- transaction.isPending
- }
- }
- static func uncompletedTransactions(_ transactions: [Transaction]) -> [Transaction] {
- transactions.filter { transaction in
- !transaction.isDone && !transaction.isPending
- }
- }
-
- override init(_ symbol: Int = -1) {
- super.init(0)//symbol)
- }
-}
-
-// 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) }
-
- var currency: String?
- var search: String?
-
- struct Args: Encodable {
- var currency: String?
- var search: String?
- }
-
- struct Response: Decodable { // list of transactions
- var transactions: [Transaction]
- }
-}
-/// A request to abort a wallet transaction by ID.
-struct AbortTransaction: WalletBackendFormattedRequest {
- func operation() -> String { return "abortTransaction" }
- func args() -> Args { return Args(transactionId: transactionId) }
-
- var transactionId: String
-
- struct Args: Encodable {
- var transactionId: String
- }
-
- struct Response: Decodable {}
-}
-/// A request to delete a wallet transaction by ID.
-struct DeleteTransaction: WalletBackendFormattedRequest {
- struct Response: Decodable {}
- func operation() -> String { return "deleteTransaction" }
- func args() -> Args { return Args(transactionId: transactionId) }
-
- var transactionId: String
-
- struct Args: Encodable {
- var transactionId: String
- }
-}
-
-// MARK: -
-extension TransactionsModel {
- /// ask wallet-core for its list of transactions filtered by searchString
- func fetchTransactionsT(currency: String? = nil, searchString: String? = nil)
- async -> [Transaction] { // might be called from a background thread itself
- do {
- let request = GetTransactions(currency: currency, search: searchString)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response.transactions
- } catch {
- return []
- }
- }
- /// fetch transactions from Wallet-Core. No networking involved
- @MainActor func fetchTransactionsM(currency: String? = nil, searchString: String? = nil)
- async -> [Transaction] { // M for MainActor
- await fetchTransactionsT(currency: currency, searchString: searchString)
- }
-
- func abortTransactionT(transactionId: String)
- async throws { // might be called from a background thread itself
- let request = AbortTransaction(transactionId: transactionId)
- let _ = try await sendRequest(request, ASYNCDELAY)
- }
- /// delete the specified transaction from Wallet-Core. No networking involved
- @MainActor func abortTransactionM(transactionId: String)
- async throws { // M for MainActor
- try await abortTransactionT(transactionId: transactionId) // call abortTransaction on main thread
- }
-
- func deleteTransactionT(transactionId: String)
- async throws { // might be called from a background thread itself
- let request = DeleteTransaction(transactionId: transactionId)
- let _ = try await sendRequest(request, ASYNCDELAY)
- }
- /// delete the specified transaction from Wallet-Core. No networking involved
- @MainActor func deleteTransactionM(transactionId: String)
- async throws { // M for MainActor
- try await deleteTransactionT(transactionId: transactionId) // call deleteTransaction on main thread
- }
-}
-
-// MARK: -
-extension TransactionsModel {
- private static var currencies: [String] = [] // names of currencies
- private static var models: [TransactionsModel] = [] // one model per currency
-
- static func model(currency: String) -> TransactionsModel {
- if let index = TransactionsModel.currencies.firstIndex(of:currency) {
- let model = TransactionsModel.models[index]
- return model
- } else { // new currency
- let model = TransactionsModel()
- TransactionsModel.models.append(model)
- TransactionsModel.currencies.append(currency)
- return model
- }
- }
-}
diff --git a/TalerWallet1/Model/WalletInitModel.swift b/TalerWallet1/Model/WalletInitModel.swift
@@ -1,72 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import SymLog
-
-private let DATABASE = "talerwalletdb-v30"
-
-class WalletInitModel: WalletModel {
- override init(_ symbol: Int = -1) { // init with 0 to disable logging for this class
- super.init(0)//symbol)
- }
-}
-// MARK: -
-/// A request to initialize Wallet-core
-fileprivate struct WalletBackendInitRequest: WalletBackendFormattedRequest {
- func operation() -> String { return "init" }
- func args() -> Args {
- return Args(persistentStoragePath: persistentStoragePath,
-// cryptoWorkerType: "sync",
- logLevel: "info") // "trace", "info", "warn", "error", "none"
- }
-
- struct Args: Encodable {
- var persistentStoragePath: String
-// var cryptoWorkerType: String
- var logLevel: String
- }
-
- var persistentStoragePath: String
-
- struct Response: Decodable {
- var versionInfo: VersionInfo
- }
-}
-// MARK: -
-/// The info returned from Wallet-core init
-struct VersionInfo: Decodable {
- var hash: String
- var version: String
- var exchange: String
- var merchant: String
- var bank: String
- var devMode: Bool
-}
-// MARK: -
-extension WalletInitModel {
- /// initalize Wallet-Core. Will do networking
- func initWalletT() // T for any Thread
- async throws -> VersionInfo? {
- let docPath = try docPath()
- let request = WalletBackendInitRequest(persistentStoragePath: docPath)
- symLog?.log("info: not main thread")
- let response = try await sendRequest(request, 0) // no Delay
- return response.versionInfo
- }
-
- private func docPath () throws -> String {
- let documentUrls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
- if (documentUrls.count > 0) {
- var storageDir = documentUrls[0]
- storageDir.appendPathComponent(DATABASE, isDirectory: false)
- storageDir.appendPathExtension("json")
- return storageDir.path
- } else { // should never happen
- symLog?.log("Yikes❗️ documentURLs empty") // TODO: symLog.error
- throw WalletBackendError.initializationError
- }
- }
-}
-
diff --git a/TalerWallet1/Model/WalletModel.swift b/TalerWallet1/Model/WalletModel.swift
@@ -4,25 +4,29 @@
*/
import Foundation
import SymLog
+import os
+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 {
+class WalletModel: ObservableObject {
+ public static let shared = WalletModel(-1)
static func className() -> String {"\(self)"}
var symLog: SymLogC?
+ let logger = Logger (subsystem: "net.taler.gnu", category: "wallet-core")
init(_ symbol: Int) { // init with 0 to disable logging for this class
self.symLog = SymLogC(symbol == 0 ? 0 : -1, funcName: Self.className())
}
func sendRequest<T: WalletBackendFormattedRequest> (_ request: T, _ delay: UInt = 0)
- async throws -> T.Response { // T for any Thread
+ async throws -> T.Response { // T for any Thread
+ symLog?.log("sending: \(request)")
let sendTime = Date.now
do {
- symLog?.log("sending: \(request)")
let (response, id) = try await WalletCore.shared.sendFormattedRequest(request: request)
let timeUsed = Date.now - sendTime
let asyncDelay: UInt = delay > 0 ? delay : UInt(ASYNCDELAY)
@@ -66,4 +70,67 @@ fileprivate struct GetTransactionById: WalletBackendFormattedRequest {
var transactionId: String
}
}
+// MARK: -
+/// The info returned from Wallet-core init
+struct VersionInfo: Decodable {
+ var hash: String
+ var version: String
+ var exchange: String
+ var merchant: String
+ var bank: String
+ var devMode: Bool
+}
+// MARK: -
+/// A request to initialize Wallet-core
+fileprivate struct InitRequest: WalletBackendFormattedRequest {
+ func operation() -> String { return "init" }
+ func args() -> Args {
+ return Args(persistentStoragePath: persistentStoragePath,
+// cryptoWorkerType: "sync",
+ logLevel: "info") // trace, info, warn, error, none
+ }
+
+ struct Args: Encodable {
+ var persistentStoragePath: String
+// var cryptoWorkerType: String
+ var logLevel: String
+ }
+
+ var persistentStoragePath: String
+
+ struct Response: Decodable {
+ var versionInfo: VersionInfo
+ }
+}
+// MARK: -
+extension WalletModel {
+ /// initalize Wallet-Core. Will do networking
+ func initWalletCoreT() async throws -> VersionInfo {
+ do { // T for any Thread
+ let docPath = try docPath()
+ let request = InitRequest(persistentStoragePath: docPath)
+ logger.info("initWalletCoreT")
+ let response = try await sendRequest(request, 0) // no Delay
+ return response.versionInfo
+ } catch {
+ logger.error("initWalletCoreT failed: \(error)")
+ throw error
+ }
+ }
+ private func docPath () throws -> String {
+ let documentUrls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
+ if (documentUrls.count > 0) {
+ var storageDir = documentUrls[0]
+ storageDir.appendPathComponent(DATABASE, isDirectory: false)
+ storageDir.appendPathExtension("json")
+ let docPath = storageDir.path
+ logger.debug("\(docPath)")
+ return docPath
+ } else { // should never happen
+ symLog?.log("Yikes❗️ documentURLs empty")
+ logger.error("documentURLs empty")
+ throw WalletBackendError.initializationError
+ }
+ }
+}
diff --git a/TalerWallet1/Model/WithdrawModel.swift b/TalerWallet1/Model/WithdrawModel.swift
@@ -1,213 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import Foundation
-import taler_swift
-import SymLog
-fileprivate let ASYNCDELAY: UInt = 0 //set e.g to 6 or 9 seconds for debugging
-
-class WithdrawModel: WalletModel {
-
- override init(_ symbol: Int = -1) { // init with 0 to disable logging for this class
- super.init(0)//symbol)
- }
-}
-
-// MARK: -
-/// The result from getWithdrawalDetailsForUri
-struct WithdrawUriInfoResponse: Decodable {
- var amount: Amount
- 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) }
-
- var talerWithdrawUri: String
- struct Args: Encodable {
- var talerWithdrawUri: String
- }
-}
-// MARK: -
-/// The result from getWithdrawalDetailsForAmount
-struct ManualWithdrawalDetails: Decodable {
- var tosAccepted: Bool // Did the user accept the current version of the exchange's terms of service?
- var amountRaw: Amount // Amount that the user will transfer to the exchange
- var amountEffective: Amount // Amount that will be added to the user's wallet balance
- var paytoUris: [String] // Ways to pay the exchange
- var ageRestrictionOptions: [Int]? // Array of ages
- var numCoins: Int? // Number of coins this amountEffective will create
-}
-/// A request to get an exchange's withdrawal details.
-fileprivate struct GetWithdrawalDetailsForAmount: WalletBackendFormattedRequest {
- typealias Response = ManualWithdrawalDetails
- func operation() -> String { return "getWithdrawalDetailsForAmount" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, amount: amount) }
-
- var exchangeBaseUrl: String
- var amount: Amount
- struct Args: Encodable {
- var exchangeBaseUrl: String
- var amount: Amount
- }
-}
-// MARK: -
-struct ExchangeTermsOfService: Decodable {
- var content: String
- var currentEtag: String
- var acceptedEtag: String?
-}
-/// 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) }
-
- var exchangeBaseUrl: String
- struct Args: Encodable {
- var exchangeBaseUrl: String
- }
-}
-// MARK: -
-/// A request to mark an exchange's terms of service as accepted.
-fileprivate struct SetExchangeTOSAccepted: WalletBackendFormattedRequest {
- struct Response: Decodable {}
- func operation() -> String { return "setExchangeTosAccepted" }
- func args() -> Args { return Args(exchangeBaseUrl: exchangeBaseUrl, etag: etag) }
-
- var exchangeBaseUrl: String
- var etag: String
-
- struct Args: Encodable {
- var exchangeBaseUrl: String
- var etag: String
- }
-}
-// MARK: -
-struct AcceptWithdrawalResponse: Decodable {
- var reservePub: String
- var confirmTransferUrl: String?
- var transactionId: String
-}
-/// 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) }
-
- var talerWithdrawUri: String
- var exchangeBaseUrl: String
-
- struct Args: Encodable {
- var talerWithdrawUri: String
- var exchangeBaseUrl: String
- }
-}
-// MARK: -
-struct AcceptManualWithdrawalResult: Decodable {
- var reservePub: String
- var exchangePaytoUris: [String]
- var transactionId: String
-}
-/// 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) }
-
- var exchangeBaseUrl: String
- var amount: Amount
- var restrictAge: Int?
-
- struct Args: Encodable {
- var exchangeBaseUrl: String
- var amount: Amount
- var restrictAge: Int?
- }
-}
-// MARK: -
-extension WithdrawModel {
- /// load withdrawal details. Networking involved
- @MainActor
- func loadWithdrawalDetailsForUriM(_ talerWithdrawUri: String) // M for MainActor
- async throws -> WithdrawUriInfoResponse {
- let request = GetWithdrawalDetailsForURI(talerWithdrawUri: talerWithdrawUri)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- @MainActor
- func loadWithdrawalDetailsForAmountM(_ exchangeBaseUrl: String, amount: Amount) // M for MainActor
- async throws -> ManualWithdrawalDetails {
- let request = GetWithdrawalDetailsForAmount(exchangeBaseUrl: exchangeBaseUrl,
- amount: amount)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- @MainActor
- func loadExchangeTermsOfServiceM(_ exchangeBaseUrl: String) // M for MainActor
- async throws -> ExchangeTermsOfService {
- let request = GetExchangeTermsOfService(exchangeBaseUrl: exchangeBaseUrl)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- @MainActor
- func setExchangeTOSAcceptedM(_ exchangeBaseUrl: String, etag: String) // M for MainActor
- async throws -> Decodable {
- let request = SetExchangeTOSAccepted(exchangeBaseUrl: exchangeBaseUrl, etag: etag)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- @MainActor
- func sendAcceptIntWithdrawalM(_ exchangeBaseUrl: String, withdrawURL: String) // M for MainActor
- async throws -> AcceptWithdrawalResponse? {
- let request = AcceptBankIntegratedWithdrawal(talerWithdrawUri: withdrawURL, exchangeBaseUrl: exchangeBaseUrl)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
- @MainActor
- func sendAcceptManualWithdrawalM(_ exchangeBaseUrl: String, amount: Amount, restrictAge: Int?) // M for MainActor
- async throws -> AcceptManualWithdrawalResult? {
- let request = AcceptManualWithdrawal(exchangeBaseUrl: exchangeBaseUrl, amount: amount, restrictAge: restrictAge)
- let response = try await sendRequest(request, ASYNCDELAY)
- return response
- }
-}
-
-// MARK: -
-extension WithdrawModel {
- private static var exchanges: [String] = [] // names of exchanges
- private static var models: [WithdrawModel] = [] // one model per exchange
-
- static func model(baseURL: String) -> WithdrawModel {
- if let index = WithdrawModel.exchanges.firstIndex(of:baseURL) {
- let model = WithdrawModel.models[index]
- return model
- } else { // new model
- let model = WithdrawModel()
- WithdrawModel.models.append(model)
- WithdrawModel.exchanges.append(baseURL)
- return model
- }
- }
-}
diff --git a/TalerWallet1/Views/Balances/BalancesListView.swift b/TalerWallet1/Views/Balances/BalancesListView.swift
@@ -12,13 +12,13 @@ import AVFoundation
struct BalancesListView: View {
private let symLog = SymLogV(0)
let navTitle: String
+ let hamburgerAction: () -> Void
@State var balances: [Balance] = []
- let model: BalancesModel?
- let hamburgerAction: () -> Void
+ @EnvironmentObject private var model: WalletModel
@State private var centsToTransfer: UInt64 = 0
- @State private var purpose: String = ""
+ @State private var summary: String = ""
@State private var showQRScanner: Bool = false
@State private var showCameraAlert: Bool = false
@@ -62,11 +62,7 @@ struct BalancesListView: View {
}
private func reloadAction() async {
- if let model {
- balances = await model.fetchBalancesM()
- } else {
- balances = []
- }
+ balances = await model.balancesM()
}
var body: some View {
@@ -75,7 +71,7 @@ struct BalancesListView: View {
let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear
#endif
Content(symLog: symLog, balances: $balances,
- centsToTransfer: $centsToTransfer, purpose: $purpose,
+ centsToTransfer: $centsToTransfer, summary: $summary,
reloadAction: reloadAction)
.navigationTitle(navTitle)
.navigationBarTitleDisplayMode(.automatic)
@@ -111,7 +107,7 @@ extension BalancesListView {
@AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
@Binding var balances: [Balance]
@Binding var centsToTransfer: UInt64
- @Binding var purpose: String
+ @Binding var summary: String
var reloadAction: () async -> Void
var body: some View {
@@ -121,11 +117,9 @@ extension BalancesListView {
#endif
Group { // necessary for .backslide transition (bug in SwiftUI)
List(balances, id: \.self) { balance in
- let model = TransactionsModel.model(currency: balance.available.currencyStr)
BalancesSectionView(balance: balance,
centsToTransfer: $centsToTransfer,
- purpose: $purpose,
- model: model)
+ summary: $summary)
}
.refreshable {
symLog?.log("refreshing")
diff --git a/TalerWallet1/Views/Balances/BalancesSectionView.swift b/TalerWallet1/Views/Balances/BalancesSectionView.swift
@@ -18,8 +18,9 @@ struct BalancesSectionView: View {
private let symLog = SymLogV()
var balance:Balance
@Binding var centsToTransfer: UInt64
- @Binding var purpose: String
- var model: TransactionsModel?
+ @Binding var summary: String
+
+ @EnvironmentObject private var model: WalletModel
@State private var isShowingDetailView = false
@State private var buttonSelected: Int? = nil
@@ -29,15 +30,33 @@ struct BalancesSectionView: View {
@State private var pendingTransactions: [Transaction] = []
@State private var uncompletedTransactions: [Transaction] = []
- func dummyTransaction (_ transactionId: String) async throws {}
+// func dummyTransaction (_ transactionId: String) async throws {}
func reloadOneAction(_ transactionId: String) async throws -> Transaction {
- if let model {
- return try await model.getTransactionByIdT(transactionId)
- } else {
- throw WalletBackendError.walletCoreError
+ return try await model.getTransactionByIdT(transactionId)
+ }
+
+ func computePending(currency: String) -> (Amount, Amount) {
+ var incoming = Amount(currency: currency, value: 0)
+ var outgoing = Amount(currency: currency, value: 0)
+ for transaction in pendingTransactions {
+ let effective = transaction.common.amountEffective
+ if currency == effective.currencyStr {
+ do {
+ if transaction.common.incoming() {
+ incoming = try incoming + effective
+ } else {
+ outgoing = try outgoing + effective
+ }
+ } catch {
+ // TODO: log error
+ symLog.log(error.localizedDescription)
+ }
+ }
}
+ return (incoming, outgoing)
}
+
var body: some View {
#if DEBUG
let _ = Self._printChanges()
@@ -45,27 +64,19 @@ struct BalancesSectionView: View {
#endif
let currency = balance.available.currencyStr
let reloadCompleted = {
- if let model {
- transactions = await model.fetchTransactionsT(currency: currency)
- completedTransactions = TransactionsModel.completedTransactions(transactions)
- }
+ transactions = await model.fetchTransactionsT(currency: currency)
+ completedTransactions = WalletModel.completedTransactions(transactions)
}
let reloadPending = {
- if let model {
- transactions = await model.fetchTransactionsT(currency: currency)
- pendingTransactions = TransactionsModel.pendingTransactions(transactions)
- }
+ transactions = await model.fetchTransactionsT(currency: currency)
+ pendingTransactions = WalletModel.pendingTransactions(transactions)
}
let reloadUncompleted = {
- if let model {
- transactions = await model.fetchTransactionsT(currency: currency)
- uncompletedTransactions = TransactionsModel.uncompletedTransactions(transactions)
- }
+ transactions = await model.fetchTransactionsT(currency: currency)
+ uncompletedTransactions = WalletModel.uncompletedTransactions(transactions)
}
- let deleteAction = model?.deleteTransactionT ?? dummyTransaction
- let abortAction = model?.abortTransactionT ?? dummyTransaction
-
- let p2pModel = Peer2peerModel.model(baseURL: currency)
+ let deleteAction = model.deleteTransactionT // dummyTransaction
+ let abortAction = model.abortTransactionT
Section {
// if "KUDOS" == currency && !balance.available.isZero {
@@ -74,18 +85,16 @@ struct BalancesSectionView: View {
// }
HStack(spacing: 0) {
NavigationLink(destination: LazyView {
- SendAmount(model: p2pModel,
- amountAvailable: balance.available,
- centsToTransfer: $centsToTransfer,
- purpose: $purpose)
+ SendAmount(amountAvailable: balance.available,
+ centsToTransfer: $centsToTransfer,
+ summary: $summary)
}, tag: 1, selection: $buttonSelected
) { EmptyView() }.frame(width: 0).opacity(0).hidden()
NavigationLink(destination: LazyView {
- RequestPayment(model: p2pModel,
- scopeInfo: balance.scopeInfo,
- centsToTransfer: $centsToTransfer,
- purpose: $purpose)
+ RequestPayment(scopeInfo: balance.scopeInfo,
+ centsToTransfer: $centsToTransfer,
+ summary: $summary)
}, tag: 2, selection: $buttonSelected
) { EmptyView() }.frame(width: 0).opacity(0).hidden()
@@ -100,22 +109,22 @@ struct BalancesSectionView: View {
) { EmptyView() }.frame(width: 0).opacity(0).hidden()
BalanceRowView(amount: balance.available, sendAction: {
-print("button: Send \(currency)")
+//print("button: Send \(currency)")
buttonSelected = 1 // will trigger SendAmount NavigationLink
}, recvAction: {
-print("button: Request Payment: \(currency)")
+//print("button: Request Payment: \(currency)")
buttonSelected = 2 // will trigger RequestPayment NavigationLink
}, rowAction: {
-print("button: Transactions: \(currency)")
+//print("button: Transactions: \(currency)")
buttonSelected = 3 // will trigger TransactionList NavigationLink
})
}
let hasPending = pendingTransactions.count > 0
if hasPending {
- let hasPendingIn = !balance.pendingIncoming.isZero
- let hasPendingOut = !balance.pendingOutgoing.isZero
+ let (pendingIncoming, pendingOutgoing) = computePending(currency: currency)
+
NavigationLink {
-let _ = print("button: Pending Transactions: \(currency)")
+//let _ = print("button: Pending Transactions: \(currency)")
LazyView {
TransactionsListView(navTitle: String(localized: "Pending"), currency: currency,
transactions: pendingTransactions,
@@ -126,11 +135,17 @@ let _ = print("button: Pending Transactions: \(currency)")
}
} label: {
VStack(spacing: 6) {
- if hasPendingIn {
- PendingRowView(amount: balance.pendingIncoming, incoming: true)
+ var rows = 0
+ if !pendingIncoming.isZero {
+ PendingRowView(amount: pendingIncoming, incoming: true)
+ let _ = (rows+=1)
+ }
+ if !pendingOutgoing.isZero {
+ PendingRowView(amount: pendingOutgoing, incoming: false)
+ let _ = (rows+=1)
}
- if hasPendingOut {
- PendingRowView(amount: balance.pendingOutgoing, incoming: false)
+ if rows == 0 {
+ Text("Some pending transactions")
}
}
}
@@ -138,7 +153,7 @@ let _ = print("button: Pending Transactions: \(currency)")
let hasUncompleted = uncompletedTransactions.count > 0
if hasUncompleted {
NavigationLink {
-let _ = print("button: Uncompleted Transactions: \(currency)")
+//let _ = print("button: Uncompleted Transactions: \(currency)")
LazyView {
TransactionsListView(navTitle: String(localized: "Uncompleted"), currency: currency,
transactions: uncompletedTransactions,
@@ -156,11 +171,10 @@ let _ = print("button: Uncompleted Transactions: \(currency)")
Text(currency)
.font(.title)
} .task {
- if let model {
- transactions = await model.fetchTransactionsT(currency: currency)
- pendingTransactions = TransactionsModel.pendingTransactions(transactions)
- uncompletedTransactions = TransactionsModel.uncompletedTransactions(transactions)
- }
+ let response = await model.fetchTransactionsT(currency: currency)
+ transactions = response
+ pendingTransactions = WalletModel.pendingTransactions(response)
+ uncompletedTransactions = WalletModel.uncompletedTransactions(response)
}
} // body
}
@@ -168,21 +182,18 @@ let _ = print("button: Uncompleted Transactions: \(currency)")
#if DEBUG
fileprivate struct BindingViewContainer : View {
@State var centsToTransfer: UInt64 = 333
- @State private var purpose: String = "bla-bla"
+ @State private var summary: String = "bla-bla"
var body: some View {
let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
let balance = Balance(available: try! Amount(fromString: LONGCURRENCY + ":0.1"),
- pendingIncoming: try! Amount(fromString: LONGCURRENCY + ":4.8"),
- pendingOutgoing: try! Amount(fromString: LONGCURRENCY + ":3.25"),
- hasPendingTransactions: true,
+ scopeInfo: scopeInfo,
requiresUserInput: false,
- scopeInfo: scopeInfo)
+ hasPendingTransactions: true)
List {
BalancesSectionView(balance: balance,
centsToTransfer: $centsToTransfer,
- purpose: $purpose,
- model: nil)
+ summary: $summary)
}
}
}
diff --git a/TalerWallet1/Views/Exchange/ExchangeListView.swift b/TalerWallet1/Views/Exchange/ExchangeListView.swift
@@ -10,35 +10,27 @@ import SymLog
struct ExchangeListView: View {
private let symLog = SymLogV(0)
let navTitle: String
-
- var model: ExchangeModel?
var hamburgerAction: () -> Void
+ @EnvironmentObject private var model: WalletModel
+
@State private var exchanges: [Exchange] = []
// source of truth for the value the user enters in currencyField for exchange withdrawals
@State private var centsToTransfer: UInt64 = 0 // TODO: different values for different currencies?
func reloadAction() async -> Void {
- if let model {
exchanges = await model.listExchangesM()
- } else {
- exchanges = []
- }
}
func addExchange(_ exUrl: String) -> Void {
Task {
- if let model {
- symLog.log("adding: \(exUrl)")
- do {
- try await model.addExchange(url: exUrl)
- symLog.log("added: \(exUrl)")
- } catch { // TODO: error handling - couldn't add exchangeURL
- symLog.log("error: \(error)")
- }
- } else {
- symLog.log("no model, cannot add \(exUrl)")
+ symLog.log("adding: \(exUrl)")
+ do {
+ try await model.addExchange(url: exUrl)
+ symLog.log("added: \(exUrl)")
+ } catch { // TODO: error handling - couldn't add exchangeURL
+ symLog.log("error: \(error)")
}
}
}
diff --git a/TalerWallet1/Views/Exchange/ExchangeSectionView.swift b/TalerWallet1/Views/Exchange/ExchangeSectionView.swift
@@ -13,7 +13,6 @@ struct ExchangeRowView: View {
@State private var buttonSelected: Int? = nil
var body: some View {
let baseURL = exchange.exchangeBaseUrl
- let model = WithdrawModel.model(baseURL: baseURL)
HStack(spacing: 0) { // can't use the built in Label because it adds the accessory arrow
Text(baseURL.trimURL())
@@ -24,7 +23,6 @@ struct ExchangeRowView: View {
) { EmptyView() }.frame(width: 0).opacity(0)
NavigationLink(destination: LazyView {
ManualWithdraw(exchange: exchange,
- model: model,
centsToTransfer: $centsToTransfer)
}, tag: 2, selection: $buttonSelected
) { EmptyView() }.frame(width: 0).opacity(0)
diff --git a/TalerWallet1/Views/Exchange/ManualWithdraw.swift b/TalerWallet1/Views/Exchange/ManualWithdraw.swift
@@ -11,9 +11,10 @@ struct ManualWithdraw: View {
private let symLog = SymLogV()
let exchange: Exchange
- let model: WithdrawModel?
@Binding var centsToTransfer: UInt64
+ @EnvironmentObject private var model: WalletModel
+
@State var manualWithdrawalDetails: ManualWithdrawalDetails? = nil
// @State var ageMenuList: [Int] = []
@@ -50,7 +51,6 @@ struct ManualWithdraw: View {
//let _ = print(selectedAge, restrictAge)
NavigationLink(destination: LazyView {
ManualWithdrawDone(exchange: exchange,
- model: model,
centsToTransfer: centsToTransfer)
// restrictAge: restrictAge)
}) {
@@ -59,7 +59,6 @@ struct ManualWithdraw: View {
} else {
NavigationLink(destination: LazyView {
WithdrawTOSView(exchangeBaseUrl: exchange.exchangeBaseUrl,
- model: model,
viewID: VIEW_WITHDRAW_TOS,
acceptAction: nil) // pop back to here
}) {
@@ -69,7 +68,6 @@ struct ManualWithdraw: View {
}
} // tooMany
} // invalid
- Spacer()
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
@@ -82,10 +80,8 @@ struct ManualWithdraw: View {
.task(id: centsToTransfer) {
let amount = Amount.amountFromCents(currency, centsToTransfer)
do {
- if let model {
- manualWithdrawalDetails = try await model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount)
-// agePicker.setAges(ages: manualWithdrawalDetails?.ageRestrictionOptions)
- }
+ manualWithdrawalDetails = try await model.loadWithdrawalDetailsForAmountM(exchange.exchangeBaseUrl, amount: amount)
+// agePicker.setAges(ages: manualWithdrawalDetails?.ageRestrictionOptions)
} catch { // TODO: error
symLog.log(error.localizedDescription)
manualWithdrawalDetails = nil
@@ -112,7 +108,6 @@ struct ManualWithdraw_Container : View {
ageRestrictionOptions: [],
permanent: false)
ManualWithdraw(exchange: exchange,
- model: nil,
centsToTransfer: $centsToTransfer,
manualWithdrawalDetails: details)
}
diff --git a/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift b/TalerWallet1/Views/Exchange/ManualWithdrawDone.swift
@@ -11,19 +11,16 @@ struct ManualWithdrawDone: View {
let navTitle = String(localized: "Wire Transfer")
let exchange: Exchange
- let model: WithdrawModel?
let centsToTransfer: UInt64
// let restrictAge: Int?
+ @EnvironmentObject private var model: WalletModel
+
@State var acceptManualWithdrawalResult: AcceptManualWithdrawalResult?
@State var withdrawalTransaction: Transaction?
func reloadOneAction(_ transactionId: String) async throws -> Transaction {
- if let model {
- return try await model.getTransactionByIdT(transactionId)
- } else {
- throw WalletBackendError.walletCoreError
- }
+ return try await model.getTransactionByIdT(transactionId)
}
var body: some View {
@@ -47,14 +44,12 @@ struct ManualWithdrawDone: View {
DebugViewC.shared.setViewID(VIEW_WITHDRAW_ACCEPT)
}.task {
do {
- if let model {
- let amount = Amount.amountFromCents(exchange.currency!, centsToTransfer)
- let result = try await model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
- amount: amount, restrictAge: 0)
+ let amount = Amount.amountFromCents(exchange.currency!, centsToTransfer)
+ let result = try await model.sendAcceptManualWithdrawalM(exchange.exchangeBaseUrl,
+ amount: amount, restrictAge: 0)
print(result as Any)
- let transaction = try await model.getTransactionByIdT(result!.transactionId)
- withdrawalTransaction = transaction
- }
+ let transaction = try await model.getTransactionByIdT(result!.transactionId)
+ withdrawalTransaction = transaction
} catch { // TODO: error
symLog.log(error.localizedDescription)
}
@@ -76,7 +71,6 @@ struct ManualWithdrawDone_Container : View {
ageRestrictionOptions: [],
permanent: false)
ManualWithdrawDone(exchange: exchange,
- model: nil,
centsToTransfer: centsToTransfer)
}
}
diff --git a/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift b/TalerWallet1/Views/HelperViews/QRCodeDetailView.swift
@@ -8,12 +8,14 @@ import AVFoundation
struct QRCodeDetailView: View {
- var talerURI: String
+ @Binding var talerURI: String
+ let incoming: Bool
var body: some View {
if talerURI.count > 10 {
VStack {
- Text("Let the payee scan this QR code to receive:")
+ Text(incoming ? "Let the payer scan this QR code to pay:"
+ : "Let the payee scan this QR code to receive:")
.fixedSize(horizontal: false, vertical: true)
.padding(.top, 30)
.font(.title3)
@@ -35,25 +37,21 @@ struct QRCodeDetailView: View {
}
}
}
-
-
+// MARK: -
#if DEBUG
fileprivate struct ContentView: View {
- @State var isOn = false
+ @State var talerURI: String = "taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520"
var body: some View {
- VStack {
-
+ List {
+ QRCodeDetailView(talerURI: $talerURI, incoming: false)
}
}
}
struct QRCodeDetailView_Previews: PreviewProvider {
static var previews: some View {
-// ContentView()
- List {
- QRCodeDetailView(talerURI: "taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520")
- }
+ ContentView()
}
}
#endif
diff --git a/TalerWallet1/Views/Main/MainView.swift b/TalerWallet1/Views/Main/MainView.swift
@@ -75,13 +75,11 @@ extension MainView {
SidebarItem(name: balances,
sysImage: "creditcard.fill", // TODO: Wallet Icon
view: AnyView(BalancesListView(navTitle: balances,
- model: BalancesModel.model(currency: "*"),
hamburgerAction: hamburgerAction)
)),
SidebarItem(name: exchanges,
sysImage: "building.columns",
view: AnyView(ExchangeListView(navTitle: exchanges,
- model: ExchangeModel.model(),
hamburgerAction: hamburgerAction)
)),
SidebarItem(name: settings, // TODO: "About"?
diff --git a/TalerWallet1/Views/Payment/PaymentURIView.swift b/TalerWallet1/Views/Payment/PaymentURIView.swift
@@ -6,10 +6,13 @@ import SwiftUI
import AVFoundation
import SymLog
+// Will be called either by the user scanning a QR code or tapping the provided link, both from the shop's website
+// we show the user the payment details
struct PaymentURIView: View {
private let symLog = SymLogV()
- var url: URL
- var model: PaymentURIModel?
+ let url: URL
+
+ @EnvironmentObject private var model: WalletModel
@State var detailsForUri: PaymentDetailsForUri? = nil
@@ -27,16 +30,14 @@ struct PaymentURIView: View {
Task {
do {
if let detailsForUri {
- if let model {
- let confirmPayResult = try await model.confirmPayM(detailsForUri.proposalId)
- symLog.log(confirmPayResult as Any)
- if confirmPayResult.type == "done" {
- // TODO: Show Hints that Payment was successfull
- playSound(success: true)
- } else {
- // TODO: show error
- playSound(success: false)
- }
+ let confirmPayResult = try await model.confirmPayM(detailsForUri.proposalId)
+ symLog.log(confirmPayResult as Any)
+ if confirmPayResult.type == "done" {
+ // TODO: Show Hints that Payment was successfull
+ playSound(success: true)
+ } else {
+ // TODO: show error
+ playSound(success: false)
}
}
} catch { // TODO: error
@@ -59,9 +60,8 @@ struct PaymentURIView: View {
}.task {
do {
symLog.log(".task")
- if let model {
- detailsForUri = try await model.preparePayForUriM(url.absoluteString)
- }
+ let details = try await model.preparePayForUriM(url.absoluteString)
+ detailsForUri = details
} catch { // TODO: error
symLog.log(error.localizedDescription)
}
diff --git a/TalerWallet1/Views/Peer2peer/PaymentPurpose.swift b/TalerWallet1/Views/Peer2peer/PaymentPurpose.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 PaymentPurpose: View {
+ private let symLog = SymLogV()
+
+ let scopeInfo: ScopeInfo
+ let centsToTransfer: UInt64
+ let fee: String
+ @Binding var summary: String
+ @Binding var expireDays: UInt
+// var deactivateAction: () -> Void
+
+ @FocusState private var isFocused: Bool
+
+ let formatter = CurrencyFormatter.shared // TODO: based on currency
+ let buttonFont: Font = .title2
+
+ private var label: String {
+ let mag = pow(10, formatter.maximumFractionDigits)
+ return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
+ }
+
+ var body: some View {
+ let amount = Amount.amountFromCents(scopeInfo.currency, centsToTransfer)
+
+ VStack (spacing: 6) {
+ Text(amount.readableDescription)
+ Text("+ \(fee) payment fee")
+ .foregroundColor(.red)
+ VStack(alignment: .leading, spacing: 6) {
+ Text("Purpose:")
+ .padding(.top)
+ .font(.title3)
+
+ TextField("Purpose", text: $summary)
+ .font(.title)
+ .foregroundColor(WalletColors().fieldForeground) // text color
+ .background(WalletColors().fieldBackground)
+ .border(.primary)
+ .focused($isFocused)
+ .onAppear {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
+ isFocused = true // make first responder - raise keybord
+ }
+ }
+
+ HStack{
+ Spacer()
+ Text("\(summary.count)/100")
+ } // maximum 100 characters
+
+ Text("Expires in:")
+ .font(.title3)
+
+ SelectDays(selected: $expireDays, maxExpiration: 35)
+ .disabled(false)
+ .padding(.bottom)
+
+ let disabled = (expireDays == 0) || (summary.count < 1)
+
+ NavigationLink(destination: LazyView {
+ SendNow(amountToSend: nil,
+ amountToReceive: amount,
+ summary: summary, expireDays: expireDays)
+ }) {
+ Text("Invoice \(label) \(scopeInfo.currency)")
+ .font(buttonFont)
+ }
+ .buttonStyle(TalerButtonStyle(type: .prominent))
+ .disabled(disabled)
+
+ Spacer()
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .padding(.horizontal)
+ }
+ .navigationTitle("Invoice another Wallet")
+ .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
+ .onAppear {
+ DebugViewC.shared.setSheetID(VIEW_INVOICE_PURPOSE)
+ print("❗️ PaymentPurpose onAppear")
+ }
+ .onDisappear {
+ print("❗️ PaymentPurpose onDisappear")
+// deactivateAction()
+ }
+ }
+
+}
+// MARK: -
+#if DEBUG
+struct PaymentPurpose_Previews: PreviewProvider {
+ static var previews: some View {
+ let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
+ @State var summary: String = "pUrPoSe"
+ @State var expireDays: UInt = 0
+ PaymentPurpose(scopeInfo: scopeInfo,
+ centsToTransfer: 5,
+ fee: "fee",
+ summary: $summary,
+ expireDays: $expireDays)
+// { print("deactivateAction") }
+ }
+}
+#endif
diff --git a/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift b/TalerWallet1/Views/Peer2peer/ReceivePurpose.swift
@@ -1,129 +0,0 @@
-/*
- * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
- * See LICENSE.md
- */
-import SwiftUI
-import taler_swift
-import SymLog
-
-struct ReceivePurpose: View {
- private let symLog = SymLogV()
- @FocusState private var isFocused: Bool
- @State var peerPullCheck: CheckPeerPullCreditResponse?
-
- var scopeInfo: ScopeInfo
- var centsToTransfer: UInt64
- @Binding var purpose: String
- @Binding var expireDays: UInt
- var deactivateAction: () -> Void
-
- let formatter = CurrencyFormatter.shared // TODO: based on currency
-
- let buttonFont: Font = .title2
- private var label: String {
- let mag = pow(10, formatter.maximumFractionDigits)
- return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
- }
-
- func pullFee(ppCheck: CheckPeerPullCreditResponse?) -> String {
- do {
- if let p2pcheck = ppCheck {
- let fee = try p2pcheck.amountRaw - p2pcheck.amountEffective
- return fee.readableDescription
- }
- } catch {}
- return ""
- }
-
- var body: some View {
- let amount = Amount.amountFromCents(scopeInfo.currency, centsToTransfer)
- let model = Peer2peerModel.model(baseURL: scopeInfo.currency)
-
- let fee = pullFee(ppCheck: peerPullCheck)
- VStack (spacing: 6) {
- Text(amount.readableDescription)
- Text("+ \(fee) payment fee")
- .foregroundColor(.red)
- VStack(alignment: .leading, spacing: 6) {
- Text("Purpose:")
- .padding(.top)
- .font(.title3)
-
- TextField("Purpose", text: $purpose)
- .font(.title)
- .foregroundColor(WalletColors().fieldForeground) // text color
- .background(WalletColors().fieldBackground)
- .border(.primary)
- .focused($isFocused)
- .onAppear {
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
- isFocused = true // make first responder - raise keybord
- }
- }
-
- HStack{
- Spacer()
- Text("\(purpose.count)/100")
- } // maximum 100 characters
-
- Text("Expires in:")
- .font(.title3)
-
- SelectDays(selected: $expireDays, maxExpiration: 35)
- .disabled(false)
- .padding(.bottom)
-
- let buttonTitle = "Invoice \(label) \(scopeInfo.currency)"
- let disabled = (expireDays == 0) || (purpose.count < 1)
-
- NavigationLink(destination: LazyView {
- SendNow(amountToSend: amount, purpose: purpose, expireDays: expireDays, model: model)
- }) {
- Text(buttonTitle)
- .font(buttonFont)
- }
- .buttonStyle(TalerButtonStyle(type: .prominent))
- .disabled(disabled)
-
- Spacer()
- }
- .frame(maxWidth: .infinity, alignment: .leading)
- .padding(.horizontal)
- }
- .navigationTitle("Invoice another Wallet")
- .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.all))
- .onAppear {
- DebugViewC.shared.setSheetID(VIEW_INVOICE_PURPOSE)
- print("❗️ ReceivePurpose onAppear")
- }
- .onDisappear {
- print("❗️ ReceivePurpose onDisappear")
- deactivateAction()
- }
- .task {
- symLog.log(".task")
- do {
- peerPullCheck = try await model.checkPeerPullCreditM(amount, exchangeBaseUrl: scopeInfo.exchangeBaseUrl ?? nil) // TODO: exchangeBaseUrl!
- } catch { // TODO: error
- symLog.log(error.localizedDescription)
- }
- }
- }
-
-}
-// MARK: -
-#if DEBUG
-struct ReceivePurpose_Previews: PreviewProvider {
- static var previews: some View {
- let scopeInfo = ScopeInfo(type: ScopeInfo.ScopeInfoType.exchange, exchangeBaseUrl: DEMOEXCHANGE, currency: LONGCURRENCY)
- @State var purpose: String = "pUrPoSe"
- @State var expireDays: UInt = 0
- ReceivePurpose(scopeInfo: scopeInfo,
- centsToTransfer: 5,
- purpose: $purpose,
- expireDays: $expireDays) {
- print("deactivateAction")
- }
- }
-}
-#endif
diff --git a/TalerWallet1/Views/Peer2peer/RequestPayment.swift b/TalerWallet1/Views/Peer2peer/RequestPayment.swift
@@ -6,13 +6,15 @@ import SwiftUI
import taler_swift
import SymLog
+// Will be called by the user tapping "Request Payment" in the balances list
struct RequestPayment: View {
private let symLog = SymLogV()
- let model: Peer2peerModel?
var scopeInfo: ScopeInfo
@Binding var centsToTransfer: UInt64
- @Binding var purpose: String
+ @Binding var summary: String
+
+ @EnvironmentObject private var model: WalletModel
@State private var peerPullCheck: CheckPeerPullCreditResponse? = nil
@State private var expireDays: UInt = 0
@@ -26,7 +28,7 @@ struct RequestPayment: View {
let navTitle = String(localized: "Request \(currency)")
let currencyField = CurrencyField(value: $centsToTransfer, currency: currency)
- VStack(alignment: .leading, spacing: 6) {
+ ScrollView {
CurrencyInputView(currencyField: currencyField,
title: String(localized: "Amount to receive:"))
@@ -37,26 +39,19 @@ struct RequestPayment: View {
HStack {
let disabled = centsToTransfer == 0
- let deactivateAction = {
- print("❗️ deactivateAction")
- }
-
NavigationLink(destination: LazyView {
- ReceivePurpose(scopeInfo: scopeInfo,
+ PaymentPurpose(scopeInfo: scopeInfo,
centsToTransfer: centsToTransfer,
- purpose: $purpose,
- expireDays: $expireDays
- ) {
- deactivateAction()
- }
+ fee: someCoins.fee,
+ summary: $summary,
+ expireDays: $expireDays)
+// { deactivateAction() }
}) {
Text("Create invoice")
}
.buttonStyle(TalerButtonStyle(type: .prominent))
.disabled(disabled)
}
-
- Spacer()
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
@@ -72,12 +67,10 @@ struct RequestPayment: View {
.task(id: centsToTransfer) {
let amount = Amount.amountFromCents(currency, centsToTransfer)
do {
- if let model {
- let ppCheck = try await model.checkPeerPullCreditM(amount, exchangeBaseUrl: nil)
- peerPullCheck = ppCheck
- // TODO: set from exchange
+ let ppCheck = try await model.checkPeerPullCreditM(amount, exchangeBaseUrl: nil)
+ peerPullCheck = ppCheck
+ // TODO: set from exchange
// agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
- }
} catch { // TODO: error
symLog.log(error.localizedDescription)
peerPullCheck = nil
diff --git a/TalerWallet1/Views/Peer2peer/SendAmount.swift b/TalerWallet1/Views/Peer2peer/SendAmount.swift
@@ -10,12 +10,13 @@ import SymLog
struct SendAmount: View {
private let symLog = SymLogV()
- let model: Peer2peerModel?
let amountAvailable: Amount
@Binding var centsToTransfer: UInt64
- @Binding var purpose: String
+ @Binding var summary: String
- @State var peerPushCheck: CheckPeerPushDebitResponse?
+ @EnvironmentObject private var model: WalletModel
+
+ @State var peerPushCheck: CheckPeerPushDebitResponse? = nil
@State private var expireDays: UInt = 0
private func fee(ppCheck: CheckPeerPushDebitResponse?) -> String {
@@ -54,21 +55,18 @@ struct SendAmount: View {
let disabled = centsToTransfer == 0 // TODO: check amountAvailable
NavigationLink(destination: LazyView {
- SendPurpose(model: model,
- amountAvailable: amountAvailable,
- centsToSend: centsToTransfer,
- fee: fee,
- purpose: $purpose,
- expireDays: $expireDays,
- deactivateAction: {})
+ SendPurpose(amountAvailable: amountAvailable,
+ centsToTransfer: centsToTransfer,
+ fee: fee,
+ summary: $summary,
+ expireDays: $expireDays)
+// { deactivateAction() }
}) {
Text("To another wallet")
}
.buttonStyle(TalerButtonStyle(type: .prominent))
.disabled(disabled)
}
-
- Spacer()
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
@@ -84,12 +82,10 @@ struct SendAmount: View {
.task(id: centsToTransfer) {
let amount = Amount.amountFromCents(currency, centsToTransfer)
do {
- if let model {
- let ppCheck = try await model.checkPeerPushDebitM(amount)
- peerPushCheck = ppCheck
- // TODO: set from exchange
- // agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
- }
+ let ppCheck = try await model.checkPeerPushDebitM(amount)
+ peerPushCheck = ppCheck
+ // TODO: set from exchange
+// agePicker.setAges(ages: peerPushCheck?.ageRestrictionOptions)
} catch { // TODO: error
symLog.log(error.localizedDescription)
peerPushCheck = nil
@@ -101,14 +97,13 @@ struct SendAmount: View {
#if DEBUG
struct SendAmount_Container : View {
@State private var centsToTransfer: UInt64 = 510
- @State private var purpose: String = ""
+ @State private var summary: String = ""
var body: some View {
let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
- SendAmount(model: nil,
- amountAvailable: amount,
- centsToTransfer: $centsToTransfer,
- purpose: $purpose)
+ SendAmount(amountAvailable: amount,
+ centsToTransfer: $centsToTransfer,
+ summary: $summary)
}
}
diff --git a/TalerWallet1/Views/Peer2peer/SendNow.swift b/TalerWallet1/Views/Peer2peer/SendNow.swift
@@ -10,22 +10,22 @@ import SymLog
struct SendNow: View {
private let symLog = SymLogV()
- var amountToSend: Amount
- var purpose: String
- var expireDays: UInt
+ let amountToSend: Amount?
+ let amountToReceive: Amount?
+ let summary: String
+ let expireDays: UInt
- var model: Peer2peerModel?
- @State var peerPushResponse: PeerPushResponse?
+ @EnvironmentObject private var model: WalletModel
- @State var talerURI: String? = nil
+ @State var talerURI: String = ""
var body: some View {
ScrollView {
- if talerURI == nil {
+ if talerURI.isEmpty {
LoadingView(backButtonHidden: true)
} else {
VStack() {
- QRCodeDetailView(talerURI: talerURI!)
+ QRCodeDetailView(talerURI: $talerURI, incoming: amountToSend == nil)
Text("The QR code can also be copied and shared from Transactions later")
.fixedSize(horizontal: false, vertical: true)
@@ -50,29 +50,38 @@ struct SendNow: View {
symLog.log(".task")
do {
// generate talerURI
- if let model {
- let timestamp = Timestamp.inSomeDays(expireDays)
- let terms = PeerContractTerms(amount: amountToSend, summary: purpose, purse_expiration: timestamp)
+ let timestamp = Timestamp.inSomeDays(expireDays)
+ if let amountToSend {
+ let terms = PeerContractTerms(amount: amountToSend,
+ summary: summary,
+ purse_expiration: timestamp)
// TODO: user might choose baseURL
- peerPushResponse = try await model.initiatePeerPushDebitM(nil, terms: terms)
- talerURI = peerPushResponse?.talerUri
- }
+ let response = try await model.initiatePeerPushDebitM(nil, terms: terms)
+ talerURI = response.talerUri
+ } else if let amountToReceive {
+ let terms = PeerContractTerms(amount: amountToReceive,
+ summary: summary,
+ purse_expiration: timestamp)
+ // TODO: user might choose baseURL
+ let response = try await model.initiatePeerPullCreditM(nil, terms: terms)
+ talerURI = response.talerUri
+ } else { talerURI = "" }
} catch { // TODO: error
symLog.log(error.localizedDescription)
+ talerURI = ""
}
} // task
}
}
// MARK: -
-//struct SendNow_Previews: PreviewProvider {
-// static var previews: some View {
-// Group {
-// SendNow(amountToSend: <#T##Amount#>,
-// purpose: <#T##String#>,
-// expireDays: <#T##UInt#>,
-// model: <#T##Peer2peerModel#>,
-// peerPushResponse: <#T##PeerPushResponse?#>,
-// talerURI: "taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520")
-// }
-// }
-//}
+struct SendNow_Previews: PreviewProvider {
+ static var previews: some View {
+ Group {
+ SendNow(amountToSend: try! Amount(fromString: LONGCURRENCY + ":4.8"),
+ amountToReceive: nil,
+ summary: "some purpose",
+ expireDays: 0,
+ talerURI: "taler://pay-push/exchange.demo.taler.net/95ZG4D1AGFGZQ7CNQ1V49D3FT18HXKA6HQT4X3XME9YSJQVFQ520")
+ }
+ }
+}
diff --git a/TalerWallet1/Views/Peer2peer/SendPurpose.swift b/TalerWallet1/Views/Peer2peer/SendPurpose.swift
@@ -9,25 +9,24 @@ import SymLog
struct SendPurpose: View {
private let symLog = SymLogV()
@FocusState private var isFocused: Bool
- var model: Peer2peerModel?
- var amountAvailable: Amount
- var centsToSend: UInt64
- var fee: String
- @Binding var purpose: String
+ let amountAvailable: Amount
+ let centsToTransfer: UInt64
+ let fee: String
+ @Binding var summary: String
@Binding var expireDays: UInt
- var deactivateAction: () -> Void
+// var deactivateAction: () -> Void
let formatter = CurrencyFormatter.shared // TODO: based on currency
let buttonFont: Font = .title2
private var label: String {
let mag = pow(10, formatter.maximumFractionDigits)
- return formatter.string(for: Decimal(centsToSend) / mag) ?? ""
+ return formatter.string(for: Decimal(centsToTransfer) / mag) ?? ""
}
var body: some View {
- let amount = Amount.amountFromCents(amountAvailable.currencyStr, centsToSend)
+ let amount = Amount.amountFromCents(amountAvailable.currencyStr, centsToTransfer)
VStack (spacing: 6) {
Text(amount.readableDescription)
@@ -38,7 +37,7 @@ struct SendPurpose: View {
.padding(.top)
.font(.title3)
- TextField("Purpose", text: $purpose)
+ TextField("Purpose", text: $summary)
.font(.title)
.foregroundColor(WalletColors().fieldForeground) // text color
.background(WalletColors().fieldBackground)
@@ -52,7 +51,7 @@ struct SendPurpose: View {
HStack{
Spacer()
- Text("\(purpose.count)/100")
+ Text("\(summary.count)/100")
} // maximum 100 characters
Text("Expires in:")
@@ -63,13 +62,14 @@ struct SendPurpose: View {
.disabled(false)
.padding(.bottom)
- let buttonTitle = "Send \(label) \(amountAvailable.currencyStr) now"
- let disabled = (expireDays == 0) || (purpose.count < 1) // TODO: check amountAvailable
+ let disabled = (expireDays == 0) || (summary.count < 1) // TODO: check amountAvailable
NavigationLink(destination: LazyView {
- SendNow(amountToSend: amount, purpose: purpose, expireDays: expireDays, model: model)
+ SendNow(amountToSend: amount,
+ amountToReceive: nil,
+ summary: summary, expireDays: expireDays)
}) {
- Text(buttonTitle)
+ Text("Send \(label) \(amountAvailable.currencyStr) now")
.font(buttonFont)
}
.buttonStyle(TalerButtonStyle(type: .prominent))
@@ -88,7 +88,7 @@ struct SendPurpose: View {
}
.onDisappear {
print("❗️ SendPurpose onDisappear")
- deactivateAction()
+// deactivateAction()
}
.task {
symLog.log(".task")
@@ -105,18 +105,15 @@ struct SendPurpose: View {
#if DEBUG
struct SendPurpose_Previews: PreviewProvider {
static var previews: some View {
- @State var purpose: String = ""
+ @State var summary: String = ""
@State var expireDays: UInt = 0
let amount = Amount(currency: LONGCURRENCY, integer: 10, fraction: 0)
- SendPurpose(model: nil,
- amountAvailable: amount,
- centsToSend: 543,
- fee: "0,43",
- purpose: $purpose,
- expireDays: $expireDays
- ) {
- print("deactivateAction")
- }
+ SendPurpose(amountAvailable: amount,
+ centsToTransfer: 543,
+ fee: "0,43",
+ summary: $summary,
+ expireDays: $expireDays)
+// { print("deactivateAction") }
}
}
#endif
diff --git a/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift b/TalerWallet1/Views/Settings/Pending/PendingOpsListView.swift
@@ -9,8 +9,9 @@ struct PendingOpsListView: View {
private let symLog = SymLogV(0)
let navTitle = String(localized: "Pending")
+ @EnvironmentObject private var model: WalletModel
+
@State var pendingOperations: [PendingOperation] = []
- var model: PendingModel
var body: some View {
#if DEBUG
@@ -32,6 +33,7 @@ extension PendingOpsListView {
let symLog: SymLogV?
@Binding var pendingOperations: [PendingOperation]
var reloadAction: () async -> [PendingOperation]
+ @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
var body: some View {
#if DEBUG
let _ = Self._printChanges()
@@ -41,7 +43,7 @@ extension PendingOpsListView {
List(pendingOperations, id: \.self) { pendingOp in
PendingOpView(pendingOp: pendingOp)
}
- .listStyle(SidebarListStyle())
+ .listStyle(myListStyle.style).anyView
.navigationBarTitleDisplayMode(.large)
.onAppear() {
DebugViewC.shared.setViewID(VIEW_PENDING)
diff --git a/TalerWallet1/Views/Settings/SettingsView.swift b/TalerWallet1/Views/Settings/SettingsView.swift
@@ -27,6 +27,8 @@ struct SettingsView: View {
var hamburgerAction: () -> Void
+ @EnvironmentObject private var model: WalletModel
+
@State private var checkDisabled = false
@State private var withDrawDisabled = false
#if DEBUG
@@ -66,7 +68,7 @@ struct SettingsView: View {
}
if showDevelopItems { // show or hide the following items
NavigationLink { // whole row like in a tableView
- LazyView { PendingOpsListView(model: PendingModel.model()) }
+ LazyView { PendingOpsListView() }
} label: {
SettingsItem(name: String(localized: "Pending Operations"), description: String(localized: "Exchange not yet ready...")) {}
}
@@ -80,7 +82,6 @@ struct SettingsView: View {
Button("Withdraw") {
withDrawDisabled = true // don't run twice
Task {
- let model: SettingsModel = SettingsModel()
symLog.log("Withdraw TestKUDOS")
do {
try await model.loadTestKudosM()
@@ -97,7 +98,6 @@ struct SettingsView: View {
Button("Test 1") {
checkDisabled = true // don't run twice
Task {
- let model: SettingsModel = SettingsModel()
symLog.log("running integration test")
do {
try await model.runIntegrationTestM(newVersion: false)
@@ -114,7 +114,6 @@ struct SettingsView: View {
Button("Test 2") {
checkDisabled = true // don't run twice
Task {
- let model: SettingsModel = SettingsModel()
symLog.log("running integration test V2")
do {
try await model.runIntegrationTestM(newVersion: true)
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pAcceptDone.swift
@@ -0,0 +1,64 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+struct P2pAcceptDone: View {
+ private let symLog = SymLogV()
+ let navTitle = String(localized: "Received P2P")
+
+ let exchangeBaseUrl: String?
+ let url: URL
+
+ @EnvironmentObject private var model: WalletModel
+
+ @State private var confirmTransferUrl: String? = nil
+ @State private var transaction: Transaction? = nil
+
+ func reloadOneAction(_ transactionId: String) async throws -> Transaction {
+ return try await model.getTransactionByIdT(transactionId)
+ }
+
+ var body: some View {
+#if DEBUG
+ let _ = Self._printChanges()
+ let _ = symLog.vlog() // just to get the # to compare it with .onAppear & onDisappear
+#endif
+ VStack {
+ if let transaction {
+ TransactionDetailView(transaction: transaction,
+ reloadAction: reloadOneAction,
+ doneAction: { dismissTop() })
+ .navigationBarBackButtonHidden(true)
+ .interactiveDismissDisabled() // can only use "Done" button to dismiss
+ .navigationTitle(navTitle)
+ } else {
+ WithdrawProgressView(message: "Bank Confirmation")
+ .navigationTitle("Loading...")
+ }
+ }.onAppear() {
+ symLog.log("onAppear")
+ DebugViewC.shared.setSheetID(SHEET_RCV_P2P_ACCEPT)
+ }.task {
+ do {
+ if let exchangeBaseUrl {
+ let result = try await model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: url.absoluteString)
+ confirmTransferUrl = result!.confirmTransferUrl
+ transaction = try await model.getTransactionByIdT(result!.transactionId)
+ }
+ } catch { // TODO: error
+ symLog.log(error.localizedDescription)
+ }
+ }
+ }
+}
+// MARK: -
+struct P2pAcceptDone_Previews: PreviewProvider {
+ static var previews: some View {
+ P2pAcceptDone(exchangeBaseUrl: DEMOEXCHANGE,
+ url: URL(string: DEMOSHOP)!)
+ }
+}
diff --git a/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift b/TalerWallet1/Views/Sheets/P2P_Sheets/P2pReceiveURIView.swift
@@ -0,0 +1,80 @@
+/*
+ * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
+ * See LICENSE.md
+ */
+import SwiftUI
+import taler_swift
+import SymLog
+
+// Will be called either by the user scanning a QR code or tapping the provided link, from another user's SendCoins
+// we show the user the P2P details - but first the ToS must be accepted
+struct P2pReceiveURIView: View {
+ private let symLog = SymLogV()
+ let navTitle = String(localized: "Accept P2P Receive")
+
+ // the URL from the bank website
+ let url: URL
+ @EnvironmentObject private var model: WalletModel
+
+ @State private var peerPushCreditResponse: PreparePeerPushCreditResponse?
+
+ var body: some View {
+ let badURL = "Error in URL: \(url)"
+ VStack {
+ if let peerPushCreditResponse {
+ List {
+ let raw = peerPushCreditResponse.amountRaw
+ let effective = peerPushCreditResponse.amountEffective
+ let currency = raw.currencyStr
+ let fee = try! Amount.diff(raw, effective)
+ let outColor = WalletColors().transactionColor(false)
+ let inColor = WalletColors().transactionColor(true)
+
+ ThreeAmountsView(topTitle: String(localized: "Amount to receive:"),
+ topAmount: raw, fee: fee,
+ bottomTitle: String(localized: "\(currency) to be obtained:"),
+ bottomAmount: effective,
+ large: false, pending: false, incoming: true,
+ baseURL: nil)
+ }
+ .navigationTitle(navTitle)
+ let tosAccepted = true // TODO: https://bugs.gnunet.org/view.php?id=7869
+ if tosAccepted {
+ NavigationLink(destination: LazyView {
+ P2pAcceptDone(exchangeBaseUrl: nil, url: url)
+ }) {
+ Text("Confirm Withdrawal") // SHEET_WITHDRAW_ACCEPT
+ }.buttonStyle(TalerButtonStyle(type: .prominent))
+ .padding()
+ } else {
+ NavigationLink(destination: LazyView {
+ WithdrawTOSView(exchangeBaseUrl: nil,
+ viewID: SHEET_RCV_P2P_TOS,
+ acceptAction: nil) // pop back to here
+ }) {
+ Text("Check Terms of Service")
+ }.buttonStyle(TalerButtonStyle(type: .prominent))
+ .padding()
+ }
+ } else {
+ // Yikes no details or no baseURL
+// WithdrawProgressView(message: url.host ?? badURL)
+// .navigationTitle("Contacting Exchange")
+ }
+ }
+ .onAppear() {
+ symLog.log("onAppear")
+ DebugViewC.shared.setSheetID(SHEET_RCV_P2P)
+ }
+ .task {
+ do { // TODO: cancelled
+ symLog.log(".task")
+ let ppCreditResponse = try await model.preparePeerPushCreditM(url.absoluteString)
+ peerPushCreditResponse = ppCreditResponse
+ } catch { // TODO: error
+ symLog.log(error.localizedDescription)
+ peerPushCreditResponse = nil
+ }
+ }
+ }
+}
diff --git a/TalerWallet1/Views/Sheets/URLSheet.swift b/TalerWallet1/Views/Sheets/URLSheet.swift
@@ -15,11 +15,19 @@ struct URLSheet: View {
var body: some View {
Group {
- if urlCommand == .withdraw {
- WithdrawURIView(url: urlToOpen)
- } else if urlCommand == UrlCommand.pay {
- let model = PaymentURIModel.model()
- PaymentURIView(url: urlToOpen, model: model)
+ if let urlCommand {
+ switch urlCommand {
+ case .withdraw:
+ WithdrawURIView(url: urlToOpen)
+ case .pay:
+ PaymentURIView(url: urlToOpen)
+ case .payPull:
+ Text("payPull not implemented yet")
+ case .payPush:
+ P2pReceiveURIView(url: urlToOpen)
+ case .unknown:
+ Text("unknown command")
+ }
} else {
VStack { // Error view
Spacer()
diff --git a/TalerWallet1/Views/Transactions/ThreeAmounts.swift b/TalerWallet1/Views/Transactions/ThreeAmounts.swift
@@ -79,7 +79,7 @@ struct ThreeAmountsView: View {
if let status {
HStack {
Spacer()
- Text("Status: \(status)") // TODO: localize
+ Text("Status: \(status)")
.font(.title2)
}.padding()
}
@@ -103,6 +103,7 @@ struct ThreeAmounts_Previews: PreviewProvider {
Button(String(localized: "Accept"), action: {})
.buttonStyle(TalerButtonStyle(type: .prominent))
.padding(.horizontal)
+ .disabled(true)
}
}
}
diff --git a/TalerWallet1/Views/Transactions/TransactionDetailView.swift b/TalerWallet1/Views/Transactions/TransactionDetailView.swift
@@ -27,8 +27,9 @@ struct TransactionDetailView: View {
let common = transaction.common
let pending = transaction.isPending
let dateString = TalerDater.dateString(from: common.timestamp)
- let navTitle = (pending ? String(localized: "Pending ") : "") + "\(common.type)" // TODO: localize type
-
+ let localizedType = transaction.localizedType
+ let navTitle = pending ? String(localized: "Pending \(localizedType)")
+ : localizedType
Group {
List {
Text("\(dateString)")
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawAcceptDone.swift
@@ -10,19 +10,16 @@ struct WithdrawAcceptDone: View {
private let symLog = SymLogV()
let navTitle = String(localized: "Confirm with Bank")
- let exchangeBaseUrl: String
- let model: WithdrawModel?
+ let exchangeBaseUrl: String?
let url: URL
+ @EnvironmentObject private var model: WalletModel
+
@State private var confirmTransferUrl: String? = nil
@State private var transaction: Transaction? = nil
func reloadOneAction(_ transactionId: String) async throws -> Transaction {
- if let model {
return try await model.getTransactionByIdT(transactionId)
- } else {
- throw WalletBackendError.walletCoreError
- }
}
var body: some View {
@@ -47,7 +44,7 @@ struct WithdrawAcceptDone: View {
DebugViewC.shared.setSheetID(SHEET_WITHDRAW_CONFIRM)
}.task {
do {
- if let model {
+ if let exchangeBaseUrl {
let result = try await model.sendAcceptIntWithdrawalM(exchangeBaseUrl, withdrawURL: url.absoluteString)
confirmTransferUrl = result!.confirmTransferUrl
transaction = try await model.getTransactionByIdT(result!.transactionId)
@@ -62,7 +59,6 @@ struct WithdrawAcceptDone: View {
struct WithdrawAcceptDone_Previews: PreviewProvider {
static var previews: some View {
WithdrawAcceptDone(exchangeBaseUrl: DEMOEXCHANGE,
- model: nil,
url: URL(string: DEMOSHOP)!)
}
}
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawTOSView.swift
@@ -11,8 +11,9 @@ struct WithdrawTOSView: View {
let navTitle = String(localized: "Terms of Service")
- var exchangeBaseUrl: String
- var model: WithdrawModel?
+ var exchangeBaseUrl: String?
+
+ @EnvironmentObject private var model: WalletModel
@State var exchangeTOS: ExchangeTermsOfService?
let viewID: Int // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS
@@ -25,7 +26,7 @@ struct WithdrawTOSView: View {
Content(symLog: symLog, exchangeTOS: exchangeTOS, myListStyle: $myListStyle) {
Task {
do {
- if let model {
+ if let exchangeBaseUrl {
_ = try await model.setExchangeTOSAcceptedM(exchangeBaseUrl, etag: exchangeTOS!.currentEtag)
if acceptAction != nil {
acceptAction!()
@@ -42,8 +43,14 @@ struct WithdrawTOSView: View {
.navigationTitle(navTitle)
.overlay {
if exchangeTOS == nil {
- WithdrawProgressView(message: exchangeBaseUrl.trimURL())
- .navigationTitle("Loading " + navTitle)
+ if let exchangeBaseUrl {
+ WithdrawProgressView(message: exchangeBaseUrl.trimURL())
+ .navigationTitle("Loading " + navTitle)
+ } else {
+ // Yikes!
+ WithdrawProgressView(message: "No exchangeBaseUrl!")
+ .navigationTitle("Loading " + navTitle)
+ }
}
}
}.onAppear() {
@@ -54,7 +61,7 @@ struct WithdrawTOSView: View {
}
}.task {
do {
- if let model {
+ if let exchangeBaseUrl {
let someTOS = try await model.loadExchangeTermsOfServiceM(exchangeBaseUrl)
exchangeTOS = someTOS
}
diff --git a/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift b/TalerWallet1/Views/WithdrawBankIntegrated/WithdrawURIView.swift
@@ -16,7 +16,7 @@ struct WithdrawURIView: View {
// the URL from the bank website
let url: URL
- let model = WithdrawModel.model(baseURL: "global") // TODO: get baseURL from URL
+ @EnvironmentObject private var model: WalletModel
// the exchange used for this withdrawal.
@State private var exchangeBaseUrl: String? = nil
@@ -48,7 +48,7 @@ struct WithdrawURIView: View {
let tosAccepted = manualWithdrawalDetails.tosAccepted
if tosAccepted {
NavigationLink(destination: LazyView {
- WithdrawAcceptDone(exchangeBaseUrl: exchangeBaseUrl, model: model, url: url)
+ WithdrawAcceptDone(exchangeBaseUrl: exchangeBaseUrl, url: url)
}) {
Text("Confirm Withdrawal") // SHEET_WITHDRAW_ACCEPT
}.buttonStyle(TalerButtonStyle(type: .prominent))
@@ -56,7 +56,6 @@ struct WithdrawURIView: View {
} else {
NavigationLink(destination: LazyView {
WithdrawTOSView(exchangeBaseUrl: exchangeBaseUrl,
- model: model,
viewID: SHEET_WITHDRAW_TOS,
acceptAction: nil) // pop back to here
}) {