From e0033f37cf805e2cb706c49ab8361a1b44cf5a67 Mon Sep 17 00:00:00 2001 From: Alexey Sidorov Date: Mon, 24 Jul 2023 17:40:09 +0200 Subject: [PATCH 1/2] Home screen outgoing transfer --- ...StrigaBankTransferUserActionConsumer.swift | 41 ++++- .../icon-upload.imageset/Contents.json | 5 +- .../icon-upload.imageset/Icon@2x.png | Bin 0 -> 2069 bytes .../icon-upload.imageset/Icon@3x.png | Bin 0 -> 2995 bytes .../icon-upload.imageset/Upload.png | Bin 298 -> 0 bytes .../icon-upload.imageset/Upload@2x.png | Bin 534 -> 0 bytes .../icon-upload.imageset/Upload@3x.png | Bin 765 -> 0 bytes .../Resources/Base.lproj/Localizable.strings | 1 + .../Resources/en.lproj/Localizable.strings | 1 + .../Resources/fr.lproj/Localizable.strings | 1 + .../Resources/ru.lproj/Localizable.strings | 1 + .../Resources/vi.lproj/Localizable.strings | 1 + .../Withdraw/WithdrawCoordinator.swift | 4 +- .../Withdraw/WithdrawViewModel.swift | 4 +- .../Aggregator/HomeAccountsAggregator.swift | 2 +- .../HomeEthereumAccountsAggregator.swift | 6 +- .../AccountList/HomeAccountsView.swift | 19 ++- .../AccountList/HomeAccountsViewModel.swift | 43 ++--- .../Model/BankTransferRenderableAccount.swift | 110 +++++++++++- .../RendableAccount+EthereumAccount.swift | 8 +- .../Subview/HomeBankTransferAccountView.swift | 32 ++++ .../Receive/Model/SpacerReceiveItem.swift | 3 +- .../SwiftUI/Renderable/FinanceBlockView.swift | 161 ++++++++++++++++++ 23 files changed, 388 insertions(+), 55 deletions(-) create mode 100644 p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Icon@2x.png create mode 100644 p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Icon@3x.png delete mode 100644 p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Upload.png delete mode 100644 p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Upload@2x.png delete mode 100644 p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Upload@3x.png create mode 100644 p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift create mode 100644 p2p_wallet/UI/SwiftUI/Renderable/FinanceBlockView.swift diff --git a/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift b/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift index dcaee36e18..3d85c07565 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift @@ -104,7 +104,7 @@ public class StrigaBankTransferUserActionConsumer: UserActionConsumer { case let .complete(action, result): Task { [weak self] in guard let self = self else { return } - var userAction = Action( + let userAction = Action( id: action.id, accountId: action.accountId, token: action.token, @@ -175,3 +175,42 @@ public class BankTransferClaimUserAction: UserAction { lhs.id == rhs.id } } + + +public class OutgoingBankTransferUserAction: UserAction { + public static func == (lhs: OutgoingBankTransferUserAction, rhs: OutgoingBankTransferUserAction) -> Bool { + lhs.id == rhs.id + } + + /// Unique internal id to track. + public var id: String + public var accountId: String + public let token: EthereumToken? + public let amount: String? + public let receivingAddress: String + /// Abstract status. + public var status: UserActionStatus + public var createdDate: Date + public var updatedDate: Date + public var result: BankTransferClaimUserActionResult? + + public init( + id: String, + accountId: String, + token: EthereumToken?, + amount: String?, + receivingAddress: String, + status: UserActionStatus, + createdDate: Date = Date(), + updatedDate: Date = Date() + ) { + self.id = id + self.accountId = accountId + self.token = token + self.amount = amount + self.receivingAddress = receivingAddress + self.status = status + self.createdDate = createdDate + self.updatedDate = updatedDate + } +} diff --git a/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Contents.json b/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Contents.json index 0370023f0c..bf014c4641 100644 --- a/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Contents.json +++ b/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Contents.json @@ -1,17 +1,16 @@ { "images" : [ { - "filename" : "Upload.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "Upload@2x.png", + "filename" : "Icon@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "Upload@3x.png", + "filename" : "Icon@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Icon@2x.png b/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b8a72eeb2025ee6e5e710744d4066e228cb8bbeb GIT binary patch literal 2069 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 literal 0 HcmV?d00001 diff --git a/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Upload.png b/p2p_wallet/Resources/Assets.xcassets/icon-upload.imageset/Upload.png deleted file mode 100644 index 1f14eabdd46747d5db2030ddfeb211b2403469bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#{hlt4Ar*{or}*+6QV?iuUv{^;Wmg-k+(Pq@ z9&ty7D_G6e%vCNq+{Kd8V=C&+lDv3XE$74MyziA289uyE+SwX(>RMFwqQc82bAz~w zzojy|J$#$XS*)nfDEu&hX(iX8t0xwHyt;VSc80)ztXO4 zNztn*2PLel-kV3g{yW8g!iBdj0_@LaYFy?N`>##$XEn}d)nNz_zoz_WmR#NC4;|+| q{K=lib@=stzR5S=CtUqr$`~aUJ7L#8BP>xDlNf9n zweOQGkBhBTW_g}pw}lt*bwyG9l0bI#KDE&nsK2ePRAxL_9LMa`_I!*& zV%tZe&k@*3{j%I-y2=+@B)Rz0W`S;P4GN_@O}Sy6w0y;`-B(pZ5NBh zrnx`R#OA-zs@ca2*ve-O`W+YpB;Xnna19B#h6G$g0mMR}Lhc)mXcw=n2cZ=8G+&8y+;Z9e#MWZvX$&s~2xx zvg2Y#&tr>wK@Lq^UQd0_X8BEfFsX?%=)^{rsUeFQHq6-;bgsDUoVEG>g$xz;&3;d| zyew6DIdjSc_dQ&@ocfOLR>^OYIkB-~>J!%q&X!{Nleok~8f3b~JzKXWNm<^>{x@;D zm}_9(EX~VPVoP3!%}`kP<3oYP8P5ruqt?oOx+LCxwAty>p8M~!cX5jcb}R@vH-+`i zlcy7(M2Ji}W4FxJvq->f z=jKoTvxwVpl8>a*wv!145)+l@aIU)k`n!{ua=KIaq-h@)sn~h+KjBDk+7UGKx_!LZ zGrc>VGTly-Hb*@15$K+gnsa;8j*Z@y)3%)9-;=`IeB=qo^z;dmO1^Kt3tOJks`iWe z_M>LM%5hCozkE}xxqh>gFLoTCmbv}*U++mSrYDZ-o;S?4<_@^}{?gwpjiU?xZf)vP zInj|cwLyWALx^~yaTCWat?iY|MVcz~UVr~-HZSLqo_*Px(~=`}>!1U9u_vcY<~Hf}ee>`hW7v&pw;B|C#qBY7Xqr`m|78cY)B9s}omS-RfJR#WuI>$%;pXCs;B|To{2#1_&11v0=1K VGVgwHV()ek-_zC4Wt~$(697p_N~Qn+ diff --git a/p2p_wallet/Resources/Base.lproj/Localizable.strings b/p2p_wallet/Resources/Base.lproj/Localizable.strings index 35f6863ace..9b217dfb71 100644 --- a/p2p_wallet/Resources/Base.lproj/Localizable.strings +++ b/p2p_wallet/Resources/Base.lproj/Localizable.strings @@ -1475,3 +1475,4 @@ "Receiver" = "Receiver"; "Your bank account name must match the name registered to your Key App account" = "Your bank account name must match the name registered to your Key App account"; "Your IBAN" = "Your IBAN"; +"Outgoing transfer" = "Outgoing transfer"; diff --git a/p2p_wallet/Resources/en.lproj/Localizable.strings b/p2p_wallet/Resources/en.lproj/Localizable.strings index e6ca5de3e6..dbba900108 100644 --- a/p2p_wallet/Resources/en.lproj/Localizable.strings +++ b/p2p_wallet/Resources/en.lproj/Localizable.strings @@ -1474,3 +1474,4 @@ "Receiver" = "Receiver"; "Your bank account name must match the name registered to your Key App account" = "Your bank account name must match the name registered to your Key App account"; "Your IBAN" = "Your IBAN"; +"Outgoing transfer" = "Outgoing transfer"; diff --git a/p2p_wallet/Resources/fr.lproj/Localizable.strings b/p2p_wallet/Resources/fr.lproj/Localizable.strings index 93ea74e17b..e5c1f41cd4 100644 --- a/p2p_wallet/Resources/fr.lproj/Localizable.strings +++ b/p2p_wallet/Resources/fr.lproj/Localizable.strings @@ -1510,3 +1510,4 @@ "Receiver" = "Destinataire"; "Your bank account name must match the name registered to your Key App account" = "Le nom de votre compte bancaire doit correspondre au nom enregistré sur votre compte Key App"; "Your IBAN" = "Votre IBAN"; +"Outgoing transfer" = "Transfert sortant"; diff --git a/p2p_wallet/Resources/ru.lproj/Localizable.strings b/p2p_wallet/Resources/ru.lproj/Localizable.strings index bfd970f348..794b5beb56 100644 --- a/p2p_wallet/Resources/ru.lproj/Localizable.strings +++ b/p2p_wallet/Resources/ru.lproj/Localizable.strings @@ -1506,3 +1506,4 @@ "Receiver" = "Получатель"; "Your bank account name must match the name registered to your Key App account" = "Имя вашего банковского счета должно совпадать с именем, зарегистрированным в вашей учетной записи Key App."; "Your IBAN" = "Ваш IBAN"; +"Outgoing transfer" = "Исходящий перевод"; diff --git a/p2p_wallet/Resources/vi.lproj/Localizable.strings b/p2p_wallet/Resources/vi.lproj/Localizable.strings index 08c9ffb428..b349873733 100644 --- a/p2p_wallet/Resources/vi.lproj/Localizable.strings +++ b/p2p_wallet/Resources/vi.lproj/Localizable.strings @@ -1512,3 +1512,4 @@ "Receiver" = "Người nhận"; "Your bank account name must match the name registered to your Key App account" = "Tên tài khoản ngân hàng của bạn phải khớp với tên đã đăng ký với tài khoản Key App của bạn"; "Your IBAN" = "IBAN của bạn"; +"Outgoing transfer" = "chuyển đi"; diff --git a/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawCoordinator.swift b/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawCoordinator.swift index 0e1179e033..fa4f48913e 100644 --- a/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawCoordinator.swift +++ b/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawCoordinator.swift @@ -34,8 +34,8 @@ final class WithdrawCoordinator: Coordinator { .map { WithdrawCoordinator.Result.canceled }, viewModel.actionCompletedPublisher .map { WithdrawCoordinator.Result.verified } - .handleEvents(receiveOutput: { _ in - self.navigationController.popToRootViewController(animated: true) + .handleEvents(receiveOutput: { [weak self] _ in + self?.navigationController.popToRootViewController(animated: true) }) ) .prefix(1).eraseToAnyPublisher() diff --git a/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawViewModel.swift b/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawViewModel.swift index ec584fee26..4ed155b8f1 100644 --- a/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawViewModel.swift +++ b/p2p_wallet/Scenes/Main/BankTransfer/Withdraw/WithdrawViewModel.swift @@ -16,7 +16,7 @@ class WithdrawViewModel: BaseViewModel, ObservableObject { @Published var IBAN: String = "" @Published var BIC: String = "" @Published var receiver: String = "" - @Published var actionTitle: String = "Withdraw" + @Published var actionTitle: String = L10n.withdraw @Published var isDataValid = false @Published var fieldsStatuses = [WithdrawViewField: FieldStatus]() @Published var isLoading = false @@ -50,7 +50,7 @@ class WithdrawViewModel: BaseViewModel, ObservableObject { isDataValid = fields.values.filter({ status in status == .valid }).count == fields.keys.count - actionTitle = isDataValid ? L10n.withdrawal : L10n.checkYourData + actionTitle = isDataValid ? L10n.withdraw : L10n.checkYourData }) .assignWeak(to: \.fieldsStatuses, on: self) .store(in: &subscriptions) diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeAccountsAggregator.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeAccountsAggregator.swift index 4c4cec60a1..ef74ccba79 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeAccountsAggregator.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeAccountsAggregator.swift @@ -8,7 +8,7 @@ struct HomeAccountsAggregator: DataAggregator { input: ( solanaAccounts: [RenderableSolanaAccount], ethereumAccounts: [RenderableEthereumAccount], - bankTransferAccounts: [BankTransferRenderableAccount] + bankTransferAccounts: [any RenderableAccount] ) ) -> (primary: [any RenderableAccount], secondary: [any RenderableAccount]) { diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeEthereumAccountsAggregator.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeEthereumAccountsAggregator.swift index caa2339166..98d4beea09 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeEthereumAccountsAggregator.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Aggregator/HomeEthereumAccountsAggregator.swift @@ -31,7 +31,7 @@ struct HomeEthereumAccountsAggregator: DataAggregator { // Compare using fiat. if balanceInFiat >= CurrencyAmount(usd: 5) { // Balance is greater than $1, user can claim. - status = .readyToClaim + status = .ready } else { // Balance is to low. status = .balanceToLow @@ -40,7 +40,7 @@ struct HomeEthereumAccountsAggregator: DataAggregator { // Compare using crypto amount. if account.balance > 0 { // Balance is not zero - status = .readyToClaim + status = .ready } else { // Balance is to low. status = .balanceToLow @@ -49,7 +49,7 @@ struct HomeEthereumAccountsAggregator: DataAggregator { } else { // Claiming is running. - status = .isClaimming + status = .isProcessing } return RenderableEthereumAccount( diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift index dbc90ad515..62426cea97 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift @@ -74,7 +74,11 @@ struct HomeAccountsView: View { .padding(.top, 32) wrappedList(itemsCount: viewModel.accounts.count) { ForEach(viewModel.accounts, id: \.id) { - tokenCell(rendableAccount: $0, isVisiable: true) + if $0 is OutgoingBankTransferRenderableAccount { + bankTransferCell(rendableAccount: $0, isVisiable: true) + } else { + tokenCell(rendableAccount: $0, isVisiable: true) + } } } if !viewModel.hiddenAccounts.isEmpty { @@ -138,6 +142,19 @@ struct HomeAccountsView: View { .padding(.horizontal, 16) } + private func bankTransferCell( + rendableAccount: any RenderableAccount, + isVisiable: Bool + ) -> some View { + HomeBankTransferAccountView( + renderable: rendableAccount, + onTap: nil, + onButtonTap: nil + ) + .frame(height: 72) + .padding(.horizontal, 16) + } + @ViewBuilder private func wrappedList( itemsCount: Int, diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift index 3cd0bafbc1..6064e1ccef 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift @@ -107,39 +107,15 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { // bankTransferPublisher let bankTransferServicePublisher = Publishers.CombineLatest( bankTransferService.value.state - .compactMap { $0.value.wallet?.accounts.usdc }, - userActionService.$actions.map { userActions in - userActions.compactMap { $0 as? BankTransferClaimUserAction } - } + .compactMap { $0.value.wallet?.accounts }, + userActionService.$actions + ) - .compactMap { (account, actions) -> [BankTransferRenderableAccount]? in - guard - account.availableBalance > 0, - let address = try? EthereumAddress( - hex: EthereumAddresses.ERC20.usdc.rawValue, - eip55: false - ) else { return nil } - - let token = EthereumToken( - name: SolanaToken.usdc.name, - symbol: SolanaToken.usdc.symbol, - decimals: 6, - logo: URL(string: SolanaToken.usdc.logoURI ?? ""), - contractType: .erc20(contract: address) + .compactMap { (account, actions) -> [any RenderableAccount] in + BankTransferRenderableAccountFactory.renderableAccount( + accounts: account, + actions: actions ) - - let action = actions.first(where: { action in - action.id == account.accountID - }) - return [ - BankTransferRenderableAccount( - accountId: account.accountID, - token: token, - visibleAmount: account.availableBalance, - rawAmount: account.totalBalance, - status: action?.status == .processing ? .isClaimming : .readyToClaim - ) - ] } let homeAccountsAggregator = HomeAccountsAggregator() @@ -150,7 +126,8 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { bankTransferServicePublisher.prepend([]) ) .map { solanaAccounts, ethereumAccounts, bankTransferAccounts in - homeAccountsAggregator.transform(input: (solanaAccounts, ethereumAccounts, bankTransferAccounts)) + homeAccountsAggregator + .transform(input: (solanaAccounts, ethereumAccounts, bankTransferAccounts)) } .receive(on: RunLoop.main) .sink { primary, secondary in @@ -236,7 +213,7 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { let userActionService: UserActionService = Resolver.resolve() let userWalletManager: UserWalletManager = Resolver.resolve() guard - account.status != .isClaimming, + account.status != .isProcessing, let walletPubKey = userWalletManager.wallet?.account.publicKey else { return } let userAction = BankTransferClaimUserAction( diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift index ff0b1a8b6f..3c741d5359 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift @@ -1,7 +1,10 @@ import BankTransfer +import BigInt import Foundation +import KeyAppBusiness import KeyAppKitCore -import BigInt +import Web3 +import Wormhole struct BankTransferRenderableAccount: RenderableAccount { let accountId: String @@ -43,9 +46,9 @@ struct BankTransferRenderableAccount: RenderableAccount { var detail: AccountDetail { switch status { - case .readyToClaim: + case .ready: return .button(label: L10n.claim, enabled: true) - case .isClaimming: + case .isProcessing: return .button(label: L10n.claim, enabled: true) case .balanceToLow: return .text("") @@ -71,7 +74,7 @@ struct BankTransferRenderableAccount: RenderableAccount { var isLoading: Bool { switch status { - case .isClaimming: + case .isProcessing: return true default: return false @@ -84,3 +87,102 @@ private extension Int { Double(self * 10_000) } } + +struct OutgoingBankTransferRenderableAccount: RenderableAccount { + let accountId: String + let fiat: Fiat + let visibleAmount: Double + var status: RenderableEthereumAccount.Status + + var id: String { accountId } + + var icon: AccountIcon { .image(.iconUpload) } + + var wrapped: Bool { false } + + var title: String { L10n.outgoingTransfer } + + var subtitle: String { + String(format: "%.2f", visibleAmount) + " \(fiat.code)" + } + + var detail: AccountDetail { + switch status { + case .ready, .isProcessing: + return .button(label: L10n.confirm, enabled: true) + case .balanceToLow: + return .text("") + } + } + + var extraAction: AccountExtraAction? { nil } + + var tags: AccountTags { + var tags: AccountTags = [] + + if status == .balanceToLow { + if visibleAmount == 0 { + tags.insert(.hidden) + } else { + tags.insert(.ignore) + } + } + return tags + } + + var isLoading: Bool { + switch status { + case .isProcessing: + return true + default: + return false + } + } +} + +class BankTransferRenderableAccountFactory { + static func renderableAccount(accounts: UserAccounts, actions: [any UserAction]) -> [any RenderableAccount] { + var transactions = [any RenderableAccount]() + if + let usdc = accounts.usdc, + usdc.availableBalance > 0, + let address = try? EthereumAddress( + hex: EthereumAddresses.ERC20.usdc.rawValue, + eip55: false + ) { + let token = EthereumToken( + name: SolanaToken.usdc.name, + symbol: SolanaToken.usdc.symbol, + decimals: 6, + logo: URL(string: SolanaToken.usdc.logoURI ?? ""), + contractType: .erc20(contract: address) + ) + let action = actions + .compactMap { $0 as? BankTransferClaimUserAction } + .first(where: { action in + action.id == usdc.accountID + }) + transactions.append( + BankTransferRenderableAccount( + accountId: usdc.accountID, + token: token, + visibleAmount: usdc.availableBalance, + rawAmount: usdc.totalBalance, + status: action?.status == .processing ? .isProcessing : .ready + ) + ) + } + + if let eur = accounts.eur { + transactions.append( + OutgoingBankTransferRenderableAccount( + accountId: eur.accountID, + fiat: .eur, + visibleAmount: 1, + status: .ready + ) + ) + } + return transactions + } +} diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/RendableAccount+EthereumAccount.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/RendableAccount+EthereumAccount.swift index ce12aae3c2..6520cac258 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/RendableAccount+EthereumAccount.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/RendableAccount+EthereumAccount.swift @@ -47,9 +47,9 @@ struct RenderableEthereumAccount: RenderableAccount { var detail: AccountDetail { switch status { - case .readyToClaim: + case .ready: return .button(label: L10n.claim, enabled: true) - case .isClaimming: + case .isProcessing: return .button(label: L10n.claiming, enabled: false) case .balanceToLow: if let balanceInFiat = account.balanceInFiat { @@ -83,8 +83,8 @@ struct RenderableEthereumAccount: RenderableAccount { extension RenderableEthereumAccount { enum Status: Equatable { - case readyToClaim - case isClaimming + case ready + case isProcessing case balanceToLow } } diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift new file mode 100644 index 0000000000..5cef23e0d8 --- /dev/null +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift @@ -0,0 +1,32 @@ +import Foundation +import SwiftUI +import KeyAppUI + +struct HomeBankTransferAccountView: View { + let iconSize: CGFloat = 50 + let renderable: any RenderableAccount + + let onTap: (() -> Void)? + let onButtonTap: (() -> Void)? + + var body: some View { + FinanceBlockView( + leadingItem: FinanceBlockLeadingItem( + image: .image(.iconUpload), + iconSize: CGSize(width: 50, height: 50), + isWrapped: false + ), + centerItem: FinancialBlockCenterItem( + title: renderable.title, + subtitle: renderable.subtitle + ), + trailingItem: FinancialBlockTrailingItem( + isLoading: renderable.isLoading, + detail: renderable.detail + ) + ) + .onTapGesture { + onTap?() + } + } +} diff --git a/p2p_wallet/Scenes/Main/Receive/Model/SpacerReceiveItem.swift b/p2p_wallet/Scenes/Main/Receive/Model/SpacerReceiveItem.swift index 9d87621b95..c3343b0ef6 100644 --- a/p2p_wallet/Scenes/Main/Receive/Model/SpacerReceiveItem.swift +++ b/p2p_wallet/Scenes/Main/Receive/Model/SpacerReceiveItem.swift @@ -4,11 +4,12 @@ import SwiftUI struct SpacerReceiveItem { var id: String = UUID().uuidString var height: CGFloat = 8 + var width: CGFloat = 8 } extension SpacerReceiveItem: ReceiveRendableItem { func render() -> some View { Color(UIColor.clear) - .frame(height: height) + .frame(width: width, height: height) } } diff --git a/p2p_wallet/UI/SwiftUI/Renderable/FinanceBlockView.swift b/p2p_wallet/UI/SwiftUI/Renderable/FinanceBlockView.swift new file mode 100644 index 0000000000..1c462ef92c --- /dev/null +++ b/p2p_wallet/UI/SwiftUI/Renderable/FinanceBlockView.swift @@ -0,0 +1,161 @@ +import SwiftUI +import KeyAppUI + +struct FinanceBlockView: View { + @State var leadingItem: any Renderable + @State var centerItem: any Renderable + @State var trailingItem: any Renderable + + var leadingView: some View { + AnyView(leadingItem.render()) + } + + var centerView: some View { + AnyView(centerItem.render()) + } + + var trailingView: some View { + AnyView(trailingItem.render()) + } + + var body: some View { + HStack(spacing: 0) { + HStack(spacing: 12) { + leadingView + centerView + } + Spacer() + trailingView + } + .padding(.vertical, 12) + } +} + + +struct FinanceBlockView_Previews: PreviewProvider { + static var previews: some View { + FinanceBlockView( + leadingItem: FinanceBlockLeadingItem(image: .image(.iconUpload), iconSize: CGSize(width: 50, height: 50), isWrapped: false), + centerItem: FinancialBlockCenterItem( + title: "renderable.title", + subtitle: "renderable.subtitle" + ), + trailingItem: ListSpacerCellViewItem(height: 0, backgroundColor: .clear) + ) + } +} + +// MARK: - Leading + +struct FinanceBlockLeadingItem: Renderable { + typealias ViewType = FinanceBlockLeadingView + var id: String = UUID().uuidString + + // TODO: Get rid of AccountIcon + var image: AccountIcon + var iconSize: CGSize + var isWrapped: Bool + + func render() -> FinanceBlockLeadingView { + FinanceBlockLeadingView(item: self) + } +} + +struct FinanceBlockLeadingView: View { + let item: FinanceBlockLeadingItem + + var body: some View { + var anURL: URL? + var aSeed: String? + var anImage: UIImage? + switch item.image { + case let .url(url): + anURL = url + case let .image(image): + anImage = image + case let .random(seed): + aSeed = seed + } + return CoinLogoImageViewRepresentable( + size: item.iconSize.width, + args: .manual( + preferredImage: anImage, + url: anURL, + key: aSeed ?? "", + wrapped: item.isWrapped + ) + ) + .frame(width: item.iconSize.width, height: item.iconSize.height) + } +} + +// MARK: - Center + +struct FinancialBlockCenterItem: Renderable { + typealias ViewType = FinancialBlockCenterView + var id: String = UUID().uuidString + + var title: String? + var subtitle: String? + + func render() -> FinancialBlockCenterView { + FinancialBlockCenterView(item: self) + } +} + +struct FinancialBlockCenterView: View { + let item: FinancialBlockCenterItem + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + if let title = item.title { + Text(title) + .apply(style: .text2) + .foregroundColor(Color(Asset.Colors.night.color)) + } + if let subtitle = item.subtitle { + Text(subtitle) + .apply(style: .label1) + .foregroundColor(Color(Asset.Colors.mountain.color)) + } + } + } +} + +// MARK: - Trailing + +struct FinancialBlockTrailingItem: Renderable { + typealias ViewType = FinancialBlockTrailingView + + var id: String = UUID().uuidString + var isLoading: Bool + var detail: AccountDetail + var onButtonTap: (() -> Void)? + + func render() -> FinancialBlockTrailingView { + FinancialBlockTrailingView(item: self) + } +} + +struct FinancialBlockTrailingView: View { + let item: FinancialBlockTrailingItem + + var body: some View { + switch item.detail { + case let .text(text): + Text(text) + .fontWeight(.semibold) + .apply(style: .text3) + .foregroundColor(Color(Asset.Colors.night.color)) + case let .button(text, enabled): + NewTextButton( + title: text, + size: .small, + style: .primaryWhite, + isEnabled: enabled, + isLoading: item.isLoading, + action: { item.onButtonTap?() } + ) + } + } +} From f25f953cc561abfdcb709d86de8928524edd9503 Mon Sep 17 00:00:00 2001 From: Elizaveta Semenova Date: Tue, 25 Jul 2023 18:56:17 +0300 Subject: [PATCH 2/2] [PWN-9259] Finish confirm button action handlers --- .../BankTransfer/Models/UserWallet.swift | 13 +- .../Mock/MockStrigaRemoteProvider.swift | 5 + .../Striga/Models/StrigaEndpoint.swift | 26 ++- .../StrigaInitiateSEPAPaymentResponse.swift | 6 + .../Striga/Models/StrigaWithdrawalInfo.swift | 2 +- .../DataProviders/StrigaLocalProvider.swift | 2 +- .../DataProviders/StrigaRemoteProvider.swift | 9 + .../StrigaRemoteProviderImpl.swift | 6 + ...StrigaBankTransferUserDataRepository.swift | 6 + ...nkTransferOutgoingUserActionConsumer.swift | 155 ++++++++++++++++++ ...StrigaBankTransferUserActionConsumer.swift | 39 ----- .../StrigaRemoteProviderTests.swift | 23 ++- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Resolver+registerAllServices.swift | 4 + .../Resources/Base.lproj/Localizable.strings | 2 +- .../Resources/en.lproj/Localizable.strings | 2 +- .../Resources/fr.lproj/Localizable.strings | 2 +- .../Resources/ru.lproj/Localizable.strings | 2 +- .../Resources/vi.lproj/Localizable.strings | 2 +- .../Claim/StrigaClaimTransaction.swift | 2 + .../IBANDetails/IBANDetailsView.swift | 11 +- .../Scenes/Main/NewHome/HomeCoordinator.swift | 26 +-- .../AccountList/HomeAccountsView.swift | 8 +- .../AccountList/HomeAccountsViewModel.swift | 78 ++++++++- .../Model/BankTransferRenderableAccount.swift | 18 +- .../Subview/HomeBankTransferAccountView.swift | 1 - 26 files changed, 370 insertions(+), 84 deletions(-) create mode 100644 Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaInitiateSEPAPaymentResponse.swift create mode 100644 Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferOutgoingUserActionConsumer.swift diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Models/UserWallet.swift b/Packages/KeyAppKit/Sources/BankTransfer/Models/UserWallet.swift index e286f5d7e3..448f62709d 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Models/UserWallet.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Models/UserWallet.swift @@ -16,12 +16,23 @@ public struct EURUserAccount: Codable { public let iban: String? public let bic: String? public let bankAccountHolderName: String? + public let availableBalance: Int? - public init(accountID: String, currency: String, createdAt: String, enriched: Bool, iban: String? = nil, bic: String? = nil, bankAccountHolderName: String? = nil) { + public init( + accountID: String, + currency: String, + createdAt: String, + enriched: Bool, + availableBalance: Int?, + iban: String? = nil, + bic: String? = nil, + bankAccountHolderName: String? = nil + ) { self.accountID = accountID self.currency = currency self.createdAt = createdAt self.enriched = enriched + self.availableBalance = availableBalance self.iban = iban self.bic = bic self.bankAccountHolderName = bankAccountHolderName diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Mock/MockStrigaRemoteProvider.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Mock/MockStrigaRemoteProvider.swift index fd73258bac..2f2aeedb57 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Mock/MockStrigaRemoteProvider.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Mock/MockStrigaRemoteProvider.swift @@ -176,4 +176,9 @@ public final class MockStrigaRemoteProvider: StrigaRemoteProvider { StrigaGetAccountStatementResponse.Transaction(id: "a25e0dd1-8f4f-441d-a671-2f7d1e9738e6", txType: "SEPA_PAYIN_COMPLETED", bankingSenderBic: "BUKBGB22", bankingSenderIban: "GB29NWBK60161331926819") ]) } + + public func initiateSEPAPayment(userId: String, accountId: String, amount: String, iban: String, bic: String) async throws -> StrigaInitiateSEPAPaymentResponse { + fatalError() + } + } diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaEndpoint.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaEndpoint.swift index 420e5d8632..70f1a015e1 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaEndpoint.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaEndpoint.swift @@ -316,7 +316,31 @@ struct StrigaEndpoint: HTTPEndpoint { body: nil ) } - + + static func initiateSEPAPayment( + baseURL: String, + keyPair: KeyPair, + userId: String, + sourceAccountId: String, + amount: String, + iban: String, + bic: String + ) throws -> Self { + try StrigaEndpoint( + baseURL: baseURL, + path: "/wallets/send/initiate/bank", + method: .post, + keyPair: keyPair, + body: [ + "userId": .init(userId), + "sourceAccountId": .init(sourceAccountId), + "amount": .init(amount), + "destination": .init(["iban": .init(iban), + "bic": .init(bic)] as [String: KeyAppNetworking.AnyEncodable]), + ] as [String: KeyAppNetworking.AnyEncodable] + ) + } + static func getAccountStatement( baseURL: String, keyPair: KeyPair, diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaInitiateSEPAPaymentResponse.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaInitiateSEPAPaymentResponse.swift new file mode 100644 index 0000000000..50e5a41dab --- /dev/null +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaInitiateSEPAPaymentResponse.swift @@ -0,0 +1,6 @@ +import Foundation + +public struct StrigaInitiateSEPAPaymentResponse: Codable { + let challengeId: String + let dateExpires: String +} diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaWithdrawalInfo.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaWithdrawalInfo.swift index c744582569..e127e6db19 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaWithdrawalInfo.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Models/StrigaWithdrawalInfo.swift @@ -1,6 +1,6 @@ import Foundation -public struct StrigaWithdrawalInfo: Codable { +public struct StrigaWithdrawalInfo: Codable, Equatable { public let IBAN: String? public let BIC: String? public let receiver: String diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaLocalProvider.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaLocalProvider.swift index 80e0314c3b..67f2a58e8f 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaLocalProvider.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaLocalProvider.swift @@ -32,7 +32,7 @@ public actor StrigaLocalProviderImpl { private func migrate() { // Migration - let migrationKey = "StrigaLocalProviderImpl.migration12" + let migrationKey = "StrigaLocalProviderImpl.migration13" if !UserDefaults.standard.bool(forKey: migrationKey) { clear() UserDefaults.standard.set(true, forKey: migrationKey) diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProvider.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProvider.swift index e3c79a7e05..2b65aa1232 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProvider.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProvider.swift @@ -100,4 +100,13 @@ public protocol StrigaRemoteProvider: AnyObject { /// - Parameter page: Page number /// - SeeAlso: [Get Account Statement](https://docs.striga.com/reference/get-account-statement) func getAccountStatement(userId: String, accountId: String, startDate: Date, endDate: Date, page: Int) async throws -> StrigaGetAccountStatementResponse + + /// Initiate SEPA Payment + /// - Parameter userId: The Id of the user who is sending this transaction + /// - Parameter accountId: The Id of the account to debit + /// - Parameter amount: The amount denominated in the smallest divisible unit of the sending currency. For example: cents + /// - Parameter iban: IBAN of the recipient - MUST be in the name of the account holder + /// - Parameter bic: BIC of the recipient - MUST be in the name of the account holder + /// - SeeAlso: [Initiate SEPA Payment](https://docs.striga.com/reference/initiate-sepa-payment) + func initiateSEPAPayment(userId: String, accountId: String, amount: String, iban: String, bic: String) async throws -> StrigaInitiateSEPAPaymentResponse } diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProviderImpl.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProviderImpl.swift index 4b5582cdea..194eef5cfc 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProviderImpl.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/DataProviders/StrigaRemoteProviderImpl.swift @@ -268,6 +268,12 @@ extension StrigaRemoteProviderImpl: StrigaRemoteProvider { let endpoint = try StrigaEndpoint.getAccountStatement(baseURL: baseURL, keyPair: keyPair, userId: userId, accountId: accountId, startDate: startDate, endDate: endDate, page: page) return try await httpClient.request(endpoint: endpoint, responseModel: StrigaGetAccountStatementResponse.self) } + + public func initiateSEPAPayment(userId: String, accountId: String, amount: String, iban: String, bic: String) async throws -> StrigaInitiateSEPAPaymentResponse { + guard let keyPair else { throw BankTransferError.invalidKeyPair } + let endpoint = try StrigaEndpoint.initiateSEPAPayment(baseURL: baseURL, keyPair: keyPair, userId: userId, sourceAccountId: accountId, amount: amount, iban: iban, bic: bic) + return try await httpClient.request(endpoint: endpoint, responseModel: StrigaInitiateSEPAPaymentResponse.self) + } } // MARK: - Error response diff --git a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/StrigaBankTransferUserDataRepository.swift b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/StrigaBankTransferUserDataRepository.swift index 93c64bd6bd..bea8e27db9 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/StrigaBankTransferUserDataRepository.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/Striga/Repository/StrigaBankTransferUserDataRepository.swift @@ -226,6 +226,7 @@ public final class StrigaBankTransferUserDataRepository: BankTransferUserDataRep currency: eur.currency, createdAt: eur.createdAt, enriched: true, + availableBalance: eur.availableBalance, iban: response.iban, bic: response.bic, bankAccountHolderName: response.bankAccountHolderName @@ -363,6 +364,10 @@ public final class StrigaBankTransferUserDataRepository: BankTransferUserDataRep throw StrigaProviderError.invalidRateTokens } + public func initiateSEPAPayment(userId: String, accountId: String, amount: String, iban: String, bic: String) async throws -> String { + try await remoteProvider.initiateSEPAPayment(userId: userId, accountId: accountId, amount: amount, iban: iban, bic: bic).challengeId + } + // MARK: - Private private func enrichAccount(userId: String, accountId: String) async throws -> T { try await remoteProvider.enrichAccount(userId: userId, accountId: accountId) @@ -399,6 +404,7 @@ private extension UserWallet { currency: eurAccount.currency, createdAt: eurAccount.createdAt, enriched: cached?.accounts.eur?.enriched ?? false, + availableBalance: Int(eurAccount.availableBalance.amount) ?? 0, iban: cached?.accounts.eur?.iban, bic: cached?.accounts.eur?.bic, bankAccountHolderName: cached?.accounts.eur?.bankAccountHolderName diff --git a/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferOutgoingUserActionConsumer.swift b/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferOutgoingUserActionConsumer.swift new file mode 100644 index 0000000000..e1fa4915d4 --- /dev/null +++ b/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferOutgoingUserActionConsumer.swift @@ -0,0 +1,155 @@ +import Combine +import Foundation +import KeyAppBusiness +import KeyAppKitCore +import SolanaSwift + +public enum OutgoingBankTransferUserActionResult: Codable, Equatable { + case requestWithdrawInfo(receiver: String) + case initiated(challengeId: String) +} + +public enum OutgoingBankTransferUserActionEvent: UserActionEvent { + case track(OutgoingBankTransferUserAction, UserActionStatus) + case complete(OutgoingBankTransferUserAction, OutgoingBankTransferUserActionResult) + case sendFailure(OutgoingBankTransferUserAction, String) +} + +public class StrigaBankTransferOutgoingUserActionConsumer: UserActionConsumer { + public typealias Action = OutgoingBankTransferUserAction + public typealias Event = OutgoingBankTransferUserActionEvent + + public let persistence: UserActionPersistentStorage + let database: SynchronizedDatabase = .init() + + private var bankTransferService: AnyBankTransferService + + public init( + persistence: UserActionPersistentStorage, + bankTransferService: AnyBankTransferService + ) { + self.persistence = persistence + self.bankTransferService = bankTransferService + } + + public var onUpdate: AnyPublisher { + database + .onUpdate + .flatMap { data in + Publishers.Sequence(sequence: Array(data.values)) + } + .eraseToAnyPublisher() + } + + public func start() {} + + public func process(action: any UserAction) { + guard let action = action as? Action else { return } + + Task { [weak self] in + await self?.database.set(for: action.id, action) + self?.handle(event: Event.track(action, .processing)) + /// Checking if all data is available + guard + let service = self?.bankTransferService.value, + let userId = await service.repository.getUserId(), + let withdrawInfo = try await service.repository.getWithdrawalInfo(userId: userId), + let iban = withdrawInfo.IBAN, + let bic = withdrawInfo.BIC + else { + Logger.log( + event: "Striga Confirm Action", + message: "Absence of data", + logLevel: .error + ) + let regData = try? await self?.bankTransferService.value.getRegistrationData() + self?.handle(event: Event.complete(action, .requestWithdrawInfo(receiver: [regData?.firstName, regData?.lastName].compactMap({ $0 }).joined(separator: " ")))) + return + } + + do { + let result = try await service.repository.initiateSEPAPayment( + userId: userId, + accountId: action.accountId, + amount: action.amount, + iban: iban, + bic: bic + ) + self?.handle(event: Event.complete(action, .initiated(challengeId: result))) + } catch { + self?.handle(event: Event.sendFailure(action, error.localizedDescription)) + } + } + } + + public func handle(event: any UserActionEvent) { + guard let event = event as? Event else { return } + handleInternalEvent(event: event) + } + + func handleInternalEvent(event: Event) { + switch event { + case let .complete(action, result): + Task { [weak self] in + guard let self = self else { return } + let userAction = Action( + id: action.id, + accountId: action.accountId, + amount: action.amount, + status: .ready + ) + userAction.result = result + await self.database.set(for: userAction.id, userAction) + } + case let .track(action, status): + Task { [weak self] in + guard let self = self else { return } + let userAction = Action( + id: action.id, + accountId: action.accountId, + amount: action.amount, + status: status + ) + await self.database.set(for: userAction.id, userAction) + } + case let .sendFailure(action, _): + Task { [weak self] in + guard let userAction = await self?.database.get(for: action.id) else { return } + userAction.status = .error(UserActionError.networkFailure) + await self?.database.set(for: action.id, userAction) + } + } + } +} + +public class OutgoingBankTransferUserAction: UserAction { + public static func == (lhs: OutgoingBankTransferUserAction, rhs: OutgoingBankTransferUserAction) -> Bool { + lhs.id == rhs.id + } + + /// Unique internal id to track. + public let id: String + public let accountId: String + public let amount: String // In cents + /// Abstract status. + public var status: UserActionStatus + public var createdDate: Date + public var updatedDate: Date + public var result: OutgoingBankTransferUserActionResult? + + public init( + id: String, + accountId: String, + amount: String, + status: UserActionStatus, + createdDate: Date = Date(), + updatedDate: Date = Date() + ) { + self.id = id + self.accountId = accountId + self.amount = amount + self.status = status + self.createdDate = createdDate + self.updatedDate = updatedDate + } +} diff --git a/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift b/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift index 3d85c07565..de0a8f62a4 100644 --- a/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift +++ b/Packages/KeyAppKit/Sources/BankTransfer/UserAction/StrigaBankTransferUserActionConsumer.swift @@ -175,42 +175,3 @@ public class BankTransferClaimUserAction: UserAction { lhs.id == rhs.id } } - - -public class OutgoingBankTransferUserAction: UserAction { - public static func == (lhs: OutgoingBankTransferUserAction, rhs: OutgoingBankTransferUserAction) -> Bool { - lhs.id == rhs.id - } - - /// Unique internal id to track. - public var id: String - public var accountId: String - public let token: EthereumToken? - public let amount: String? - public let receivingAddress: String - /// Abstract status. - public var status: UserActionStatus - public var createdDate: Date - public var updatedDate: Date - public var result: BankTransferClaimUserActionResult? - - public init( - id: String, - accountId: String, - token: EthereumToken?, - amount: String?, - receivingAddress: String, - status: UserActionStatus, - createdDate: Date = Date(), - updatedDate: Date = Date() - ) { - self.id = id - self.accountId = accountId - self.token = token - self.amount = amount - self.receivingAddress = receivingAddress - self.status = status - self.createdDate = createdDate - self.updatedDate = updatedDate - } -} diff --git a/Packages/KeyAppKit/Tests/UnitTests/BankTransferTests/StrigaRemoteProviderTests.swift b/Packages/KeyAppKit/Tests/UnitTests/BankTransferTests/StrigaRemoteProviderTests.swift index 086dff61b8..0df55f91ff 100644 --- a/Packages/KeyAppKit/Tests/UnitTests/BankTransferTests/StrigaRemoteProviderTests.swift +++ b/Packages/KeyAppKit/Tests/UnitTests/BankTransferTests/StrigaRemoteProviderTests.swift @@ -549,7 +549,7 @@ final class StrigaRemoteProviderTests: XCTestCase { endDate: Date(), page: 1 ) - debugPrint(result.transactions.first(where: { $0.txType == "SEPA_PAYIN_COMPLETED" })) + // Assert XCTAssertEqual(result.transactions.isEmpty, false) XCTAssertEqual(result.transactions.contains(where: { $0.txType == "SEPA_PAYOUT_COMPLETED" }), false) @@ -559,6 +559,27 @@ final class StrigaRemoteProviderTests: XCTestCase { XCTAssertNotNil(result.transactions.first(where: { $0.txType == "SEPA_PAYIN_COMPLETED" })?.bankingSenderBic) } + func testInitiateSEPAPayment_SuccessfulResponse() async throws { + // Arrange + let mockData = """ + {"challengeId":"924aa8d8-a377-4d61-8761-0b98a4f3f897","dateExpires":"2023-07-25T15:56:11.231Z","transaction":{"syncedOwnerId":"b861b16e-1070-4f54-b992-219549538526","sourceAccountId":"793815a2c66152e7de19318617860ba2","iban":"GB29NWBK60161331926819","bic":"BUKBGB22","amount":"574","status":"PENDING_2FA_CONFIRMATION","txType":"SEPA_PAYOUT_INITIATED","parentWalletId":"f4df3cc6-9c60-461a-8207-05cc8e6e7207","currency":"EUR","feeEstimate":{"totalFee":"0","networkFee":"0","ourFee":"0","theirFee":"0","feeCurrency":"EUR"}},"feeEstimate":{"totalFee":"0","networkFee":"0","ourFee":"0","theirFee":"0","feeCurrency":"EUR"}} + """ + let provider = try getMockProvider(responseString: mockData, statusCode: 200) + + let result = try await provider.initiateSEPAPayment( + userId: "cecaea44-47f2-439b-99a1-a35fefaf1eb6", + accountId: "4dc6ecb29d74198e9e507f8025cad011", + amount: "574", + iban: "GB29NWBK60161331926819", + bic: "BUKBGB22" + ) + + // Assert + XCTAssertNotNil(result.challengeId) + XCTAssertFalse(result.challengeId.isEmpty) + } + + // MARK: - Helper Methods func getMockProvider(responseString: String, statusCode: Int, error: Error? = nil) throws -> StrigaRemoteProvider { diff --git a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c6d7b75310..e186c115a7 100644 --- a/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/p2p_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -303,8 +303,8 @@ "repositoryURL": "https://github.com/google/promises.git", "state": { "branch": null, - "revision": "c22f76b709dc4bb6d274398259e75c191e50998a", - "version": "2.3.0" + "revision": "e70e889c0196c76d22759eb50d6a0270ca9f1d9e", + "version": "2.3.1" } }, { diff --git a/p2p_wallet/Injection/Resolver+registerAllServices.swift b/p2p_wallet/Injection/Resolver+registerAllServices.swift index 72eac91f63..7af90e6a00 100644 --- a/p2p_wallet/Injection/Resolver+registerAllServices.swift +++ b/p2p_wallet/Injection/Resolver+registerAllServices.swift @@ -418,6 +418,10 @@ extension Resolver: ResolverRegistering { persistence: resolve(), bankTransferService: resolve(), solanaAccountService: resolve() + ), + StrigaBankTransferOutgoingUserActionConsumer( + persistence: resolve(), + bankTransferService: resolve() ) ] ) diff --git a/p2p_wallet/Resources/Base.lproj/Localizable.strings b/p2p_wallet/Resources/Base.lproj/Localizable.strings index 9b217dfb71..153f551483 100644 --- a/p2p_wallet/Resources/Base.lproj/Localizable.strings +++ b/p2p_wallet/Resources/Base.lproj/Localizable.strings @@ -1475,4 +1475,4 @@ "Receiver" = "Receiver"; "Your bank account name must match the name registered to your Key App account" = "Your bank account name must match the name registered to your Key App account"; "Your IBAN" = "Your IBAN"; -"Outgoing transfer" = "Outgoing transfer"; +"Outcoming transfer" = "Outcoming transfer"; diff --git a/p2p_wallet/Resources/en.lproj/Localizable.strings b/p2p_wallet/Resources/en.lproj/Localizable.strings index dbba900108..fde2f478cb 100644 --- a/p2p_wallet/Resources/en.lproj/Localizable.strings +++ b/p2p_wallet/Resources/en.lproj/Localizable.strings @@ -1474,4 +1474,4 @@ "Receiver" = "Receiver"; "Your bank account name must match the name registered to your Key App account" = "Your bank account name must match the name registered to your Key App account"; "Your IBAN" = "Your IBAN"; -"Outgoing transfer" = "Outgoing transfer"; +"Outcoming transfer" = "Outcoming transfer"; diff --git a/p2p_wallet/Resources/fr.lproj/Localizable.strings b/p2p_wallet/Resources/fr.lproj/Localizable.strings index e5c1f41cd4..a493e4c428 100644 --- a/p2p_wallet/Resources/fr.lproj/Localizable.strings +++ b/p2p_wallet/Resources/fr.lproj/Localizable.strings @@ -1510,4 +1510,4 @@ "Receiver" = "Destinataire"; "Your bank account name must match the name registered to your Key App account" = "Le nom de votre compte bancaire doit correspondre au nom enregistré sur votre compte Key App"; "Your IBAN" = "Votre IBAN"; -"Outgoing transfer" = "Transfert sortant"; +"Outcoming transfer" = "Transfert sortant"; diff --git a/p2p_wallet/Resources/ru.lproj/Localizable.strings b/p2p_wallet/Resources/ru.lproj/Localizable.strings index 794b5beb56..9d1f8ca7cd 100644 --- a/p2p_wallet/Resources/ru.lproj/Localizable.strings +++ b/p2p_wallet/Resources/ru.lproj/Localizable.strings @@ -1506,4 +1506,4 @@ "Receiver" = "Получатель"; "Your bank account name must match the name registered to your Key App account" = "Имя вашего банковского счета должно совпадать с именем, зарегистрированным в вашей учетной записи Key App."; "Your IBAN" = "Ваш IBAN"; -"Outgoing transfer" = "Исходящий перевод"; +"Outcoming transfer" = "Исходящий перевод"; diff --git a/p2p_wallet/Resources/vi.lproj/Localizable.strings b/p2p_wallet/Resources/vi.lproj/Localizable.strings index b349873733..6b0aea7b6f 100644 --- a/p2p_wallet/Resources/vi.lproj/Localizable.strings +++ b/p2p_wallet/Resources/vi.lproj/Localizable.strings @@ -1512,4 +1512,4 @@ "Receiver" = "Người nhận"; "Your bank account name must match the name registered to your Key App account" = "Tên tài khoản ngân hàng của bạn phải khớp với tên đã đăng ký với tài khoản Key App của bạn"; "Your IBAN" = "IBAN của bạn"; -"Outgoing transfer" = "chuyển đi"; +"Outcoming transfer" = "chuyển khoản đi"; diff --git a/p2p_wallet/Scenes/Main/BankTransfer/Claim/StrigaClaimTransaction.swift b/p2p_wallet/Scenes/Main/BankTransfer/Claim/StrigaClaimTransaction.swift index e9b053574b..d735c5b087 100644 --- a/p2p_wallet/Scenes/Main/BankTransfer/Claim/StrigaClaimTransaction.swift +++ b/p2p_wallet/Scenes/Main/BankTransfer/Claim/StrigaClaimTransaction.swift @@ -98,3 +98,5 @@ struct StrigaWithdrawTransaction: StrigaWithdrawTransactionType, StrigaConfirmab return .fakeTransactionSignature(id: UUID().uuidString) } } + +extension StrigaClaimTransaction: Equatable {} diff --git a/p2p_wallet/Scenes/Main/BankTransfer/IBANDetails/IBANDetailsView.swift b/p2p_wallet/Scenes/Main/BankTransfer/IBANDetails/IBANDetailsView.swift index 9a4e3a576c..ee88bf460f 100644 --- a/p2p_wallet/Scenes/Main/BankTransfer/IBANDetails/IBANDetailsView.swift +++ b/p2p_wallet/Scenes/Main/BankTransfer/IBANDetails/IBANDetailsView.swift @@ -58,7 +58,16 @@ struct IBANDetailsView_Previews: PreviewProvider { NavigationView { IBANDetailsView( viewModel: IBANDetailsViewModel( - eurAccount: EURUserAccount(accountID: "", currency: "", createdAt: "", enriched: true, iban: "IBAN", bic: "BIC", bankAccountHolderName: "Name Surname") + eurAccount: EURUserAccount( + accountID: "", + currency: "", + createdAt: "", + enriched: true, + availableBalance: nil, + iban: "IBAN", + bic: "BIC", + bankAccountHolderName: "Name Surname" + ) ) ) .navigationTitle(L10n.euroAccount) diff --git a/p2p_wallet/Scenes/Main/NewHome/HomeCoordinator.swift b/p2p_wallet/Scenes/Main/NewHome/HomeCoordinator.swift index cc74b84002..1dd783f1b8 100644 --- a/p2p_wallet/Scenes/Main/NewHome/HomeCoordinator.swift +++ b/p2p_wallet/Scenes/Main/NewHome/HomeCoordinator.swift @@ -20,13 +20,14 @@ enum HomeNavigation: Equatable { case earn case solanaAccount(SolanaAccount) case claim(EthereumAccount, WormholeClaimUserAction?) - case bankTransferClaim(BankTransferClaimUserAction) + case bankTransferClaim(StrigaClaimTransaction) case actions([WalletActionType]) // HomeEmpty case topUpCoin(Token) case topUp // Top up via bank transfer, bank card or crypto receive case bankTransfer // Only bank transfer - case withdraw + case withdrawCalculator + case withdrawInfo(StrigaWithdrawalInfo) // Error case error(show: Bool) } @@ -183,17 +184,10 @@ final class HomeCoordinator: Coordinator { .map { _ in () } .eraseToAnyPublisher() } - case .bankTransferClaim(let userAction): + case .bankTransferClaim(let transaction): return coordinate(to: BankTransferClaimCoordinator( navigationController: navigationController, - transaction: StrigaClaimTransaction( - challengeId: userAction.result?.challengeId ?? "", - token: userAction.result?.token ?? .usdc, - amount: Double(userAction.amount ?? "") ?? 0, - feeAmount: .zero, - fromAddress: userAction.result?.fromAddress ?? "", - receivingAddress: userAction.receivingAddress - ) + transaction: transaction )) .map { _ in Void() } .eraseToAnyPublisher() @@ -254,9 +248,17 @@ final class HomeCoordinator: Coordinator { case .bankTransfer: return coordinate(to: BankTransferCoordinator(viewController: navigationController)) .eraseToAnyPublisher() - case .withdraw: + case .withdrawCalculator: return coordinate(to: WithdrawCalculatorCoordinator(navigationController: navigationController)) .eraseToAnyPublisher() + case let .withdrawInfo(model): + return coordinate(to: WithdrawCoordinator( + navigationController: navigationController, + strategy: .confirmation, + withdrawalInfo: model + )) + .map { _ in () } // TODO: Handle other actions here + .eraseToAnyPublisher() case let .topUpCoin(token): // SOL, USDC if [Token.nativeSolana, .usdc].contains(token) { diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift index 62426cea97..54a39dc673 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsView.swift @@ -75,7 +75,7 @@ struct HomeAccountsView: View { wrappedList(itemsCount: viewModel.accounts.count) { ForEach(viewModel.accounts, id: \.id) { if $0 is OutgoingBankTransferRenderableAccount { - bankTransferCell(rendableAccount: $0, isVisiable: true) + bankTransferCell(rendableAccount: $0, isVisible: true) } else { tokenCell(rendableAccount: $0, isVisiable: true) } @@ -144,12 +144,12 @@ struct HomeAccountsView: View { private func bankTransferCell( rendableAccount: any RenderableAccount, - isVisiable: Bool + isVisible: Bool ) -> some View { HomeBankTransferAccountView( renderable: rendableAccount, - onTap: nil, - onButtonTap: nil + onTap: { viewModel.invoke(for: rendableAccount, event: .tap) }, + onButtonTap: { viewModel.invoke(for: rendableAccount, event: .extraButtonTap) } ) .frame(height: 72) .padding(.horizontal, 16) diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift index 6064e1ccef..9d01d683c2 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/HomeAccountsViewModel.swift @@ -147,14 +147,12 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { .store(in: &subscriptions) userActionService.$actions - .compactMap { - $0.compactMap { $0 as? BankTransferClaimUserAction } - } - .flatMap { $0.publisher } - .handleEvents(receiveOutput: { val in + .compactMap { $0.compactMap { $0 as? BankTransferClaimUserAction } } + .flatMap(\.publisher) + .handleEvents(receiveOutput: { [weak self] val in switch val.status { - case .error(let error): - self.notificationService.showDefaultErrorNotification() + case .error: + self?.notificationService.showDefaultErrorNotification() default: break } @@ -162,9 +160,28 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { .filter { $0.status == .ready } .receive(on: RunLoop.main) .sink { [weak self] action in - self?.navigation.send(.bankTransferClaim(action)) + guard let result = action.result else { return } + self?.handleClaim(result: result, in: action) }.store(in: &subscriptions) + userActionService.$actions + .compactMap { $0.compactMap { $0 as? OutgoingBankTransferUserAction } } + .flatMap(\.publisher) + .filter { $0.status != .pending && $0.status != .processing } + .receive(on: RunLoop.main) + .sink { [weak self] action in + switch action.status { + case .ready: + guard let result = action.result else { return } + self?.handleOutgoingConfirm(result: result, in: action) + case .error: + self?.notificationService.showDefaultErrorNotification() + default: + break + } + } + .store(in: &subscriptions) + analyticsManager.log(event: .claimAvailable(claim: available(.ethAddressEnabled))) } @@ -204,11 +221,27 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { case let renderableAccount as BankTransferRenderableAccount: handleBankTransfer(account: renderableAccount) + case let renderableAccount as OutgoingBankTransferRenderableAccount: + handleBankTransfer(account: renderableAccount) + default: break } } + private func handleBankTransfer(account: OutgoingBankTransferRenderableAccount) { + let userActionService: UserActionService = Resolver.resolve() + guard account.status != .isProcessing else { return } + let userAction = OutgoingBankTransferUserAction( + id: account.id, + accountId: account.accountId, + amount: String(account.rawAmount), + status: .processing + ) + // Execute and emit action. + userActionService.execute(action: userAction) + } + private func handleBankTransfer(account: BankTransferRenderableAccount) { let userActionService: UserActionService = Resolver.resolve() let userWalletManager: UserWalletManager = Resolver.resolve() @@ -232,6 +265,33 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { userActionService.execute(action: userAction) } + private func handleOutgoingConfirm(result: OutgoingBankTransferUserActionResult, in action: OutgoingBankTransferUserAction) { + switch result { + case let .initiated(challengeId): + self.navigation.send(.bankTransferClaim(StrigaClaimTransaction( + challengeId: challengeId, + token: .usdc, + amount: Double(action.amount) ?? 0 / 100, + feeAmount: .zero, + fromAddress: "", + receivingAddress: "" + ))) + case let .requestWithdrawInfo(receiver): + self.navigation.send(.withdrawInfo(StrigaWithdrawalInfo(receiver: receiver))) + } + } + + private func handleClaim(result: BankTransferClaimUserActionResult, in action: BankTransferClaimUserAction) { + self.navigation.send(.bankTransferClaim(StrigaClaimTransaction( + challengeId: action.result?.challengeId ?? "", + token: action.result?.token ?? .usdc, + amount: Double(action.amount ?? "") ?? 0, + feeAmount: .zero, + fromAddress: action.result?.fromAddress ?? "", + receivingAddress: action.receivingAddress + ))) + } + func actionClicked(_ action: WalletActionType) { switch action { case .receive: @@ -254,7 +314,7 @@ final class HomeAccountsViewModel: BaseViewModel, ObservableObject { case .topUp: navigation.send(.topUp) case .withdraw: - navigation.send(.withdraw) + navigation.send(.withdrawCalculator) } } diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift index 3c741d5359..127e21de33 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Model/BankTransferRenderableAccount.swift @@ -1,5 +1,6 @@ import BankTransfer import BigInt +import BigDecimal import Foundation import KeyAppBusiness import KeyAppKitCore @@ -91,8 +92,13 @@ private extension Int { struct OutgoingBankTransferRenderableAccount: RenderableAccount { let accountId: String let fiat: Fiat - let visibleAmount: Double + let rawAmount: Int var status: RenderableEthereumAccount.Status + private var amount: CurrencyAmount { + CurrencyAmount(value: BigDecimal(floatLiteral: visibleAmount), currencyCode: fiat.code) + } + + var visibleAmount: Double { Double(rawAmount) / 100 } var id: String { accountId } @@ -100,10 +106,10 @@ struct OutgoingBankTransferRenderableAccount: RenderableAccount { var wrapped: Bool { false } - var title: String { L10n.outgoingTransfer } + var title: String { L10n.outcomingTransfer } var subtitle: String { - String(format: "%.2f", visibleAmount) + " \(fiat.code)" + CurrencyFormatter(defaultValue: "", hideSymbol: true).string(amount: amount).appending(" \(fiat.code)") } var detail: AccountDetail { @@ -173,12 +179,12 @@ class BankTransferRenderableAccountFactory { ) } - if let eur = accounts.eur { + if let eur = accounts.eur, let balance = eur.availableBalance, balance > 0 { transactions.append( - OutgoingBankTransferRenderableAccount( + OutgoingBankTransferRenderableAccount( accountId: eur.accountID, fiat: .eur, - visibleAmount: 1, + rawAmount: balance, status: .ready ) ) diff --git a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift index 5cef23e0d8..61915f0ea3 100644 --- a/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift +++ b/p2p_wallet/Scenes/Main/NewHome/Subview/AccountList/Subview/HomeBankTransferAccountView.swift @@ -3,7 +3,6 @@ import SwiftUI import KeyAppUI struct HomeBankTransferAccountView: View { - let iconSize: CGFloat = 50 let renderable: any RenderableAccount let onTap: (() -> Void)?