From 5481ce00efdc4d4025d152fbde95c9e855c65b02 Mon Sep 17 00:00:00 2001 From: Aleksandr Pavliuk Date: Wed, 25 Jul 2018 13:54:21 +0300 Subject: [PATCH 1/3] [NY-1597] Wallet functionality --- .../ContextMenu/Model/ContextMenuItem.swift | 20 +- Nynja.xcodeproj/project.pbxproj | 570 +++++++++++++++++- .../Models/Desc/DescExtension.swift | 12 +- .../Models/Feature/FeatureExtension.swift | 17 + Nynja/Generated/AssetsConstants.swift | 22 +- Nynja/Generated/LocalizableConstants.swift | 80 ++- Nynja/KeychainService/KeychainService.swift | 8 +- .../MessageFactory/MessageFactory.swift | 6 +- .../MessageFactoryProtocol.swift | 2 +- ...ynjaContextMenuItemsFactory+Messages.swift | 90 ++- .../UI/Extensions/StringExtensions.swift | 13 + .../Model/ChatListMessageCellModel.swift | 2 +- .../Material/Base/MaterialTextContainer.swift | 1 + .../Material/Config/NynjaMTIConfig.swift | 1 + .../Material/MaterialTextField.swift | 24 +- .../TextInput/Material/MaterialTextView.swift | 41 +- .../Modules/Contacts/ContactsProtocols.swift | 40 +- .../Interactor/ContactsInteractor.swift | 10 +- .../Presenter/ContactsPresenter.swift | 19 +- .../View/TransferCoinstoContactsView.swift | 52 ++ .../ContactsViewController.swift | 59 +- .../WireFrame/ContactsWireframe.swift | 78 ++- Nynja/Modules/Main/MainProtocols.swift | 2 +- .../Main/Presenter/MainPresenter.swift | 15 +- .../Main/WireFrame/MainWireframe.swift | 15 +- .../MessageInteractor+Translation.swift | 19 +- .../Interactor/MessageInteractor.swift | 7 - .../Message/Presenter/MessagePresenter.swift | 65 +- .../Message/Protocols/MessageProtocols.swift | 4 + .../View/MessageVC+ContextMenuDelegate.swift | 8 + .../ChatCells/BaseChatCell/BaseChatCell.swift | 2 +- .../Cells/ChatCells/MessageCellFactory.swift | 2 +- .../Cells/Models/BaseChatCellModel.swift | 2 + .../Cells/Views/Base/MessageViewFactory.swift | 8 +- .../TableView/Cells/Views/Info/InfoView.swift | 2 +- .../Views/Message/MessagePaymentView.swift | 173 ------ .../Views/Message/MessageTransferView.swift | 209 +++++++ .../Message/WireFrame/MessageWireframe.swift | 8 +- .../Interactor/PaymentInteractor.swift | 150 ----- Nynja/Modules/Payment/PaymentProtocols.swift | 52 -- .../Payment/Presenter/PaymentPresenter.swift | 78 --- .../Payment/View/PaymentViewController.swift | 212 ------- .../Interactor/ProfileInteractor.swift | 1 - .../Profile/Presenter/ProfilePresenter.swift | 39 +- Nynja/Modules/Profile/ProfileProtocols.swift | 16 +- .../View/DetailsView/ProfileDetailsView.swift | 33 +- .../Profile/View/ProfileViewController.swift | 50 +- .../CreateWallet/CreateWalletProtocols.swift | 37 ++ .../Entities/CreateWalletParams.swift | 13 + .../Entities/CreateWalletState.swift | 33 + .../CreateWalletValidationViewModel.swift | 14 + .../Interactor/CreateWalletInteractor.swift | 108 ++++ .../Presenter/CreateWalletPresenter.swift | 81 +++ .../View/CreateWalletViewController.swift | 238 ++++++++ .../WireFrame/CreateWalletWireFrame.swift | 60 ++ .../Payment/Entities/PaymentModel.swift | 13 + .../Entities/PaymentTableCellModel.swift | 19 + .../Entities/PaymentViewControllerModel.swift | 9 + .../Interactor/PaymentInteractor.swift | 215 +++++++ .../Payment/PaymentProtocols.swift | 48 ++ .../Payment/Presenter/PaymentPresenter.swift | 151 +++++ .../Payment/View/PaymentTableViewCell.swift | 25 +- .../Payment/View/PaymentViewController.swift | 303 ++++++++++ .../Payment/WireFrame/PaymentWireFrame.swift | 24 +- .../SeedBackupWalletInputParams.swift | 13 + .../SeedBackupWalletOutputParams.swift | 14 + .../SeedBackupWalletInteractor.swift | 69 +++ .../Presenter/SeedBackupWalletPresenter.swift | 50 ++ .../SeedBackupWalletProtocols.swift | 32 + .../SeedBackupWalletCollectionViewCell.swift | 152 +++++ .../View/SeedBackupWalletViewController.swift | 311 ++++++++++ .../WireFrame/SeedBackupWalletWireFrame.swift | 61 ++ .../SeedVerificationWalletInput.swift | 14 + .../SeedVerificationWalletInteractor.swift | 59 ++ .../SeedVerificationWalletPresenter.swift | 65 ++ .../SeedVerificationWalletProtocols.swift | 41 ++ ...VerificationWalletCollectionViewCell.swift | 115 ++++ ...SeedVerificationWalletViewController.swift | 281 +++++++++ .../SeedVerificationWalletWireFrame.swift | 59 ++ .../TransferDetailsInteractor.swift | 26 + .../Presenter/TransferDetailsPresenter.swift | 41 ++ .../TransferDetailsProtocols.swift | 27 + .../View/TransferDetailsViewController.swift | 446 ++++++++++++++ .../WireFrame/TransferDetailsWireFrame.swift | 43 ++ .../Entities/TransferHistoryTableModel.swift | 24 + .../TransferHistoryInteractor.swift | 42 ++ .../Presenter/TransferHistoryPresenter.swift | 52 ++ .../TransferHistoryProtocols.swift | 41 ++ .../TransferHistoryTableDataSource.swift | 76 +++ .../View/TransferHistoryCell.swift | 120 ++++ .../View/TransferHistoryHeaderView.swift | 30 + .../View/TransferHistoryViewController.swift | 209 +++++++ .../WireFrame/TransferHistoryWireFrame.swift | 54 ++ .../Entities/WalletBalancesOutput.swift | 13 + .../Entities/WalletBalancesViewModel.swift | 30 + .../Entities/WalletBalancesWallet.swift | 14 + .../Interactor/WalletBalancesInteractor.swift | 90 +++ .../Presenter/WalletBalancesPresenter.swift | 130 ++++ .../View/WalletBalancesTableViewCell.swift | 14 +- .../View/WalletBalancesViewController.swift | 349 +++++++++++ .../WalletBalancesProtocols.swift | 47 ++ .../WireFrame/WalletBalancesWireFrame.swift | 40 +- .../Entities/WalletDetailsViewInput.swift | 12 + .../Interactor/WalletDetailsInteractor.swift | 55 ++ .../Presenter/WalletDetailsPresenter.swift | 71 +++ .../View/WalletDetailsViewController.swift | 286 +++++++++ .../WalletDetailsProtocols.swift | 42 ++ .../WireFrame/WalletDetailsWireFrame.swift | 57 ++ .../Interactor/WalletBalancesInteractor.swift | 50 -- .../Presenter/WalletBalancesPresenter.swift | 78 --- .../View/WalletBalancesViewController.swift | 158 ----- .../WalletBalancesProtocols.swift | 43 -- .../Contents.json | 2 +- .../ic_arrow_down.pdf} | Bin 3902 -> 4045 bytes .../Contents.json | 2 +- .../ic_arrow_down_up.pdf} | Bin 3990 -> 4054 bytes .../Contents.json | 15 + .../ic_context_blockchain.pdf} | Bin 5465 -> 4448 bytes .../Contents.json | 15 + .../ic_context_transfer.pdf | Bin 0 -> 4402 bytes .../Contents.json | 2 +- .../ic_eth_disabled.pdf | Bin 0 -> 5699 bytes .../ic_qr_code_wallet.imageset/Contents.json | 15 + .../ic_qr_code_wallet.pdf | Bin 0 -> 6258 bytes .../Contents.json | 15 + .../ic_contacts_empty.pdf | Bin 0 -> 4750 bytes .../ic_wallet_absent.imageset/Contents.json | 15 + .../ic_wallet_absent.pdf | Bin 0 -> 5444 bytes .../ic_back.imageset/Contents.json | 14 +- .../ic_back.imageset/ic_back.pdf | Bin 4030 -> 4035 bytes Nynja/Resources/Constants.swift | 4 +- Nynja/Resources/en.lproj/Localizable.strings | 61 +- Nynja/Resources/ru.lproj/Localizable.strings | 62 +- .../ModelAdapters/ContactServices.swift | 22 +- .../ModelAdapters/ProfileServices.swift | 31 +- Nynja/Services/MQTT/MQTTServiceWallet.swift | 6 +- Nynja/Services/Models/SendModel.swift | 4 +- .../ServiceFactory/ServiceFactory.swift | 25 +- Nynja/Services/StorageService.swift | 2 +- .../Entities/WalletServiceResult.swift | 21 + .../Entities/WalletServiceWallet.swift | 17 + .../WalletService/WalletService.swift | 140 ++++- Nynja/Utils/Money/CryptoCurrency.swift | 30 +- .../TextInputValidationService.swift | 158 ++++- .../WalletFundingNetworkResponse.swift | 12 + .../WalletFundingNetworkRouter.swift | 52 ++ .../WalletFundingNetworkService.swift | 33 + Nynja/WalletMQTTModel.swift | 5 +- Podfile | 2 +- Podfile.lock | 14 +- 150 files changed, 7238 insertions(+), 1434 deletions(-) create mode 100644 Nynja/Modules/Contacts/View/TransferCoinstoContactsView.swift delete mode 100644 Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessagePaymentView.swift create mode 100644 Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessageTransferView.swift delete mode 100644 Nynja/Modules/Payment/Interactor/PaymentInteractor.swift delete mode 100644 Nynja/Modules/Payment/PaymentProtocols.swift delete mode 100644 Nynja/Modules/Payment/Presenter/PaymentPresenter.swift delete mode 100644 Nynja/Modules/Payment/View/PaymentViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/CreateWalletProtocols.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletParams.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletState.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletValidationViewModel.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/Interactor/CreateWalletInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/Presenter/CreateWalletPresenter.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/CreateWallet/WireFrame/CreateWalletWireFrame.swift create mode 100644 Nynja/Modules/Wallet Flows/Payment/Entities/PaymentModel.swift create mode 100644 Nynja/Modules/Wallet Flows/Payment/Entities/PaymentTableCellModel.swift create mode 100644 Nynja/Modules/Wallet Flows/Payment/Entities/PaymentViewControllerModel.swift create mode 100644 Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/Payment/PaymentProtocols.swift create mode 100644 Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift rename Nynja/Modules/{ => Wallet Flows}/Payment/View/PaymentTableViewCell.swift (81%) create mode 100644 Nynja/Modules/Wallet Flows/Payment/View/PaymentViewController.swift rename Nynja/Modules/{ => Wallet Flows}/Payment/WireFrame/PaymentWireFrame.swift (62%) create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletInputParams.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletOutputParams.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/Interactor/SeedBackupWalletInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/Presenter/SeedBackupWalletPresenter.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/SeedBackupWalletProtocols.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletCollectionViewCell.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedBackup/WireFrame/SeedBackupWalletWireFrame.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedVerification/Entities/SeedVerificationWalletInput.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedVerification/Presenter/SeedVerificationWalletPresenter.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedVerification/SeedVerificationWalletProtocols.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletCollectionViewCell.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/SeedVerification/WireFrame/SeedVerificationWalletWireFrame.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferDetails/Interactor/TransferDetailsInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferDetails/Presenter/TransferDetailsPresenter.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferDetails/TransferDetailsProtocols.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferDetails/View/TransferDetailsViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferDetails/WireFrame/TransferDetailsWireFrame.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/Entities/TransferHistoryTableModel.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/Interactor/TransferHistoryInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/Presenter/TransferHistoryPresenter.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/TransferHistoryProtocols.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/View/TableView/TransferHistoryTableDataSource.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryCell.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryHeaderView.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/TransferHistory/WireFrame/TransferHistoryWireFrame.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesOutput.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesViewModel.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesWallet.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletBalances/Presenter/WalletBalancesPresenter.swift rename Nynja/Modules/{ => Wallet Flows}/WalletBalances/View/WalletBalancesTableViewCell.swift (89%) create mode 100644 Nynja/Modules/Wallet Flows/WalletBalances/View/WalletBalancesViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletBalances/WalletBalancesProtocols.swift rename Nynja/Modules/{ => Wallet Flows}/WalletBalances/WireFrame/WalletBalancesWireFrame.swift (50%) create mode 100644 Nynja/Modules/Wallet Flows/WalletDetails/Entities/WalletDetailsViewInput.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletDetails/Presenter/WalletDetailsPresenter.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletDetails/View/WalletDetailsViewController.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletDetails/WalletDetailsProtocols.swift create mode 100644 Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift delete mode 100644 Nynja/Modules/WalletBalances/Interactor/WalletBalancesInteractor.swift delete mode 100644 Nynja/Modules/WalletBalances/Presenter/WalletBalancesPresenter.swift delete mode 100644 Nynja/Modules/WalletBalances/View/WalletBalancesViewController.swift delete mode 100644 Nynja/Modules/WalletBalances/WalletBalancesProtocols.swift rename Nynja/Resources/Assets.xcassets/Wallet/{ic_btc.imageset => ic_arrow_down_transfer.imageset}/Contents.json (82%) rename Nynja/Resources/Assets.xcassets/Wallet/{ic_eos.imageset/ic_eos.pdf => ic_arrow_down_transfer.imageset/ic_arrow_down.pdf} (81%) rename Nynja/Resources/Assets.xcassets/{ic_arrow_right-1.imageset => Wallet/ic_arrow_down_up_transfer.imageset}/Contents.json (80%) rename Nynja/Resources/Assets.xcassets/{ic_arrow_right-1.imageset/icons_ic_arrow_right.pdf => Wallet/ic_arrow_down_up_transfer.imageset/ic_arrow_down_up.pdf} (80%) create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_context_blockchain.imageset/Contents.json rename Nynja/Resources/Assets.xcassets/Wallet/{ic_btc.imageset/ic_btc.pdf => ic_context_blockchain.imageset/ic_context_blockchain.pdf} (60%) create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_context_transfer.imageset/Contents.json create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_context_transfer.imageset/ic_context_transfer.pdf rename Nynja/Resources/Assets.xcassets/Wallet/{ic_eos.imageset => ic_eth_disabled.imageset}/Contents.json (81%) create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_eth_disabled.imageset/ic_eth_disabled.pdf create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_qr_code_wallet.imageset/Contents.json create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_qr_code_wallet.imageset/ic_qr_code_wallet.pdf create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_transfer_history_empty.imageset/Contents.json create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_transfer_history_empty.imageset/ic_contacts_empty.pdf create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/Contents.json create mode 100644 Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/ic_wallet_absent.pdf create mode 100644 Nynja/Services/WalletService/Entities/WalletServiceResult.swift create mode 100644 Nynja/Services/WalletService/Entities/WalletServiceWallet.swift create mode 100644 Nynja/WalletFundingNetworkService/Entities/WalletFundingNetworkResponse.swift create mode 100644 Nynja/WalletFundingNetworkService/WalletFundingNetworkRouter.swift create mode 100644 Nynja/WalletFundingNetworkService/WalletFundingNetworkService.swift diff --git a/Frameworks/NynjaUIKit/NynjaUIKit/Views/ContextMenu/Model/ContextMenuItem.swift b/Frameworks/NynjaUIKit/NynjaUIKit/Views/ContextMenu/Model/ContextMenuItem.swift index 4f0ade68f..b475bf0da 100644 --- a/Frameworks/NynjaUIKit/NynjaUIKit/Views/ContextMenu/Model/ContextMenuItem.swift +++ b/Frameworks/NynjaUIKit/NynjaUIKit/Views/ContextMenu/Model/ContextMenuItem.swift @@ -107,7 +107,25 @@ extension ContextMenuItem { } public enum Action { - case star, reply, edit, forward, translate, untranslate, chooseLanguage, transcribe, untranscribe, copy, copyConvertion, delete, share, saveToGallery, saveToDownloads, showInChat, marketPlace + case star + case reply + case edit + case forward + case translate + case untranslate + case chooseLanguage + case transcribe + case untranscribe + case copy + case copyConvertion + case delete + case share + case saveToGallery + case saveToDownloads + case showInChat + case marketPlace + case transfers + case showInBlockchain } public enum LayoutRepresentation { diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index e95210b3e..c9991dcc5 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -2051,6 +2051,62 @@ FB16E79D20EFCF15009FA203 /* CryptoMoney.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB16E79C20EFCF15009FA203 /* CryptoMoney.swift */; }; FB351F9220AC22A70042ACB1 /* ImagePreviewTransitionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB351F9120AC22A70042ACB1 /* ImagePreviewTransitionHelpers.swift */; }; FB3FE46120AC1C4400F2B847 /* ImagePreviewTransitionProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB3FE46020AC1C4400F2B847 /* ImagePreviewTransitionProtocols.swift */; }; + FB60B40A215E600500B59279 /* WalletFundingNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B406215E600500B59279 /* WalletFundingNetworkService.swift */; }; + FB60B40C215E600500B59279 /* WalletFundingNetworkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B407215E600500B59279 /* WalletFundingNetworkRouter.swift */; }; + FB60B40E215E600500B59279 /* WalletFundingNetworkResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B409215E600500B59279 /* WalletFundingNetworkResponse.swift */; }; + FB60B445215E611A00B59279 /* WalletDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B413215E611900B59279 /* WalletDetailsPresenter.swift */; }; + FB60B447215E611A00B59279 /* WalletDetailsWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B415215E611900B59279 /* WalletDetailsWireFrame.swift */; }; + FB60B449215E611A00B59279 /* WalletDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B417215E611900B59279 /* WalletDetailsViewController.swift */; }; + FB60B44B215E611A00B59279 /* WalletDetailsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B418215E611900B59279 /* WalletDetailsProtocols.swift */; }; + FB60B44D215E611A00B59279 /* WalletDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B41A215E611900B59279 /* WalletDetailsInteractor.swift */; }; + FB60B44F215E611A00B59279 /* WalletDetailsViewInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B41C215E611900B59279 /* WalletDetailsViewInput.swift */; }; + FB60B451215E611A00B59279 /* TransferDetailsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B41F215E611900B59279 /* TransferDetailsPresenter.swift */; }; + FB60B453215E611A00B59279 /* TransferDetailsWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B421215E611900B59279 /* TransferDetailsWireFrame.swift */; }; + FB60B455215E611A00B59279 /* TransferDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B423215E611900B59279 /* TransferDetailsViewController.swift */; }; + FB60B457215E611A00B59279 /* TransferDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B425215E611900B59279 /* TransferDetailsInteractor.swift */; }; + FB60B459215E611A00B59279 /* TransferDetailsProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B426215E611900B59279 /* TransferDetailsProtocols.swift */; }; + FB60B45B215E611A00B59279 /* CreateWalletProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B428215E611900B59279 /* CreateWalletProtocols.swift */; }; + FB60B45D215E611A00B59279 /* CreateWalletPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B42A215E611900B59279 /* CreateWalletPresenter.swift */; }; + FB60B45F215E611A00B59279 /* CreateWalletWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B42C215E611900B59279 /* CreateWalletWireFrame.swift */; }; + FB60B461215E611A00B59279 /* CreateWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B42E215E611900B59279 /* CreateWalletViewController.swift */; }; + FB60B463215E611A00B59279 /* CreateWalletInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B430215E611900B59279 /* CreateWalletInteractor.swift */; }; + FB60B465215E611A00B59279 /* CreateWalletState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B432215E611900B59279 /* CreateWalletState.swift */; }; + FB60B467215E611A00B59279 /* CreateWalletValidationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B433215E611900B59279 /* CreateWalletValidationViewModel.swift */; }; + FB60B469215E611A00B59279 /* CreateWalletParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B434215E611900B59279 /* CreateWalletParams.swift */; }; + FB60B46B215E611A00B59279 /* TransferHistoryProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B436215E611900B59279 /* TransferHistoryProtocols.swift */; }; + FB60B46D215E611A00B59279 /* TransferHistoryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B438215E611900B59279 /* TransferHistoryPresenter.swift */; }; + FB60B46F215E611A00B59279 /* TransferHistoryWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B43A215E611900B59279 /* TransferHistoryWireFrame.swift */; }; + FB60B471215E611A00B59279 /* TransferHistoryTableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B43D215E611900B59279 /* TransferHistoryTableDataSource.swift */; }; + FB60B473215E611A00B59279 /* TransferHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B43E215E611900B59279 /* TransferHistoryViewController.swift */; }; + FB60B475215E611A00B59279 /* TransferHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B43F215E611900B59279 /* TransferHistoryHeaderView.swift */; }; + FB60B477215E611A00B59279 /* TransferHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B440215E611900B59279 /* TransferHistoryCell.swift */; }; + FB60B479215E611A00B59279 /* TransferHistoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B442215E611900B59279 /* TransferHistoryInteractor.swift */; }; + FB60B47B215E611A00B59279 /* TransferHistoryTableModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B444215E611900B59279 /* TransferHistoryTableModel.swift */; }; + FB60B480215E618300B59279 /* WalletServiceResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B47E215E618200B59279 /* WalletServiceResult.swift */; }; + FB60B482215E618300B59279 /* WalletServiceWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B47F215E618200B59279 /* WalletServiceWallet.swift */; }; + FB60B48F215E671400B59279 /* WalletBalancesWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B48C215E671300B59279 /* WalletBalancesWallet.swift */; }; + FB60B490215E671400B59279 /* WalletBalancesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B48D215E671300B59279 /* WalletBalancesViewModel.swift */; }; + FB60B491215E671400B59279 /* WalletBalancesOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B48E215E671300B59279 /* WalletBalancesOutput.swift */; }; + FB60B496215E671D00B59279 /* PaymentViewControllerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B493215E671D00B59279 /* PaymentViewControllerModel.swift */; }; + FB60B497215E671D00B59279 /* PaymentTableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B494215E671D00B59279 /* PaymentTableCellModel.swift */; }; + FB60B498215E671D00B59279 /* PaymentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B495215E671D00B59279 /* PaymentModel.swift */; }; + FB60B4C0215E770400B59279 /* SeedVerificationWalletPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4A7215E770300B59279 /* SeedVerificationWalletPresenter.swift */; }; + FB60B4C1215E770400B59279 /* SeedVerificationWalletWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4A9215E770300B59279 /* SeedVerificationWalletWireFrame.swift */; }; + FB60B4C2215E770400B59279 /* SeedVerificationWalletProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4AA215E770300B59279 /* SeedVerificationWalletProtocols.swift */; }; + FB60B4C3215E770400B59279 /* SeedVerificationWalletCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4AC215E770300B59279 /* SeedVerificationWalletCollectionViewCell.swift */; }; + FB60B4C4215E770400B59279 /* SeedVerificationWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4AD215E770300B59279 /* SeedVerificationWalletViewController.swift */; }; + FB60B4C5215E770400B59279 /* SeedVerificationWalletInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4AF215E770300B59279 /* SeedVerificationWalletInteractor.swift */; }; + FB60B4C6215E770400B59279 /* SeedVerificationWalletInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4B1215E770300B59279 /* SeedVerificationWalletInput.swift */; }; + FB60B4C7215E770400B59279 /* SeedBackupWalletPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4B4215E770300B59279 /* SeedBackupWalletPresenter.swift */; }; + FB60B4C8215E770400B59279 /* SeedBackupWalletProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4B5215E770300B59279 /* SeedBackupWalletProtocols.swift */; }; + FB60B4C9215E770400B59279 /* SeedBackupWalletWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4B7215E770300B59279 /* SeedBackupWalletWireFrame.swift */; }; + FB60B4CA215E770400B59279 /* SeedBackupWalletCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4B9215E770300B59279 /* SeedBackupWalletCollectionViewCell.swift */; }; + FB60B4CB215E770400B59279 /* SeedBackupWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4BA215E770300B59279 /* SeedBackupWalletViewController.swift */; }; + FB60B4CC215E770400B59279 /* SeedBackupWalletInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4BC215E770300B59279 /* SeedBackupWalletInteractor.swift */; }; + FB60B4CD215E770400B59279 /* SeedBackupWalletInputParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4BE215E770300B59279 /* SeedBackupWalletInputParams.swift */; }; + FB60B4CE215E770400B59279 /* SeedBackupWalletOutputParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4BF215E770300B59279 /* SeedBackupWalletOutputParams.swift */; }; + FB60B4D0215E780900B59279 /* MessageTransferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4CF215E780800B59279 /* MessageTransferView.swift */; }; + FB60B4D2215E794600B59279 /* TransferCoinstoContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60B4D1215E794500B59279 /* TransferCoinstoContactsView.swift */; }; FB61D64D214FEC7D00CB2A1F /* FontsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD885752147F9640099B8C3 /* FontsConstants.swift */; }; FB61D64E214FEC7D00CB2A1F /* FontsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD885752147F9640099B8C3 /* FontsConstants.swift */; }; FB61D64F214FEC7E00CB2A1F /* FontsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD885752147F9640099B8C3 /* FontsConstants.swift */; }; @@ -2086,7 +2142,6 @@ FBCE83D320E52352003B7558 /* PaymentInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83CD20E52351003B7558 /* PaymentInteractor.swift */; }; FBCE83D520E52397003B7558 /* MQTTServiceWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */; }; FBCE83D720E523A8003B7558 /* WalletMQTTModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83D620E523A8003B7558 /* WalletMQTTModel.swift */; }; - FBCE83DC20E52437003B7558 /* MessagePaymentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83DB20E52437003B7558 /* MessagePaymentView.swift */; }; FBCE83E020E52496003B7558 /* ContactServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83DE20E52496003B7558 /* ContactServices.swift */; }; FBCE83E120E52496003B7558 /* ProfileServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83DF20E52496003B7558 /* ProfileServices.swift */; }; FBCE840D20E525A6003B7558 /* HTTPResponseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83FA20E525A5003B7558 /* HTTPResponseResult.swift */; }; @@ -3979,6 +4034,62 @@ FB16E79C20EFCF15009FA203 /* CryptoMoney.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoMoney.swift; sourceTree = ""; }; FB351F9120AC22A70042ACB1 /* ImagePreviewTransitionHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePreviewTransitionHelpers.swift; sourceTree = ""; }; FB3FE46020AC1C4400F2B847 /* ImagePreviewTransitionProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePreviewTransitionProtocols.swift; sourceTree = ""; }; + FB60B406215E600500B59279 /* WalletFundingNetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFundingNetworkService.swift; sourceTree = ""; }; + FB60B407215E600500B59279 /* WalletFundingNetworkRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFundingNetworkRouter.swift; sourceTree = ""; }; + FB60B409215E600500B59279 /* WalletFundingNetworkResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletFundingNetworkResponse.swift; sourceTree = ""; }; + FB60B413215E611900B59279 /* WalletDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsPresenter.swift; sourceTree = ""; }; + FB60B415215E611900B59279 /* WalletDetailsWireFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsWireFrame.swift; sourceTree = ""; }; + FB60B417215E611900B59279 /* WalletDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsViewController.swift; sourceTree = ""; }; + FB60B418215E611900B59279 /* WalletDetailsProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsProtocols.swift; sourceTree = ""; }; + FB60B41A215E611900B59279 /* WalletDetailsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsInteractor.swift; sourceTree = ""; }; + FB60B41C215E611900B59279 /* WalletDetailsViewInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletDetailsViewInput.swift; sourceTree = ""; }; + FB60B41F215E611900B59279 /* TransferDetailsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferDetailsPresenter.swift; sourceTree = ""; }; + FB60B421215E611900B59279 /* TransferDetailsWireFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferDetailsWireFrame.swift; sourceTree = ""; }; + FB60B423215E611900B59279 /* TransferDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferDetailsViewController.swift; sourceTree = ""; }; + FB60B425215E611900B59279 /* TransferDetailsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferDetailsInteractor.swift; sourceTree = ""; }; + FB60B426215E611900B59279 /* TransferDetailsProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferDetailsProtocols.swift; sourceTree = ""; }; + FB60B428215E611900B59279 /* CreateWalletProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletProtocols.swift; sourceTree = ""; }; + FB60B42A215E611900B59279 /* CreateWalletPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletPresenter.swift; sourceTree = ""; }; + FB60B42C215E611900B59279 /* CreateWalletWireFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletWireFrame.swift; sourceTree = ""; }; + FB60B42E215E611900B59279 /* CreateWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletViewController.swift; sourceTree = ""; }; + FB60B430215E611900B59279 /* CreateWalletInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletInteractor.swift; sourceTree = ""; }; + FB60B432215E611900B59279 /* CreateWalletState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletState.swift; sourceTree = ""; }; + FB60B433215E611900B59279 /* CreateWalletValidationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletValidationViewModel.swift; sourceTree = ""; }; + FB60B434215E611900B59279 /* CreateWalletParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateWalletParams.swift; sourceTree = ""; }; + FB60B436215E611900B59279 /* TransferHistoryProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryProtocols.swift; sourceTree = ""; }; + FB60B438215E611900B59279 /* TransferHistoryPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryPresenter.swift; sourceTree = ""; }; + FB60B43A215E611900B59279 /* TransferHistoryWireFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryWireFrame.swift; sourceTree = ""; }; + FB60B43D215E611900B59279 /* TransferHistoryTableDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryTableDataSource.swift; sourceTree = ""; }; + FB60B43E215E611900B59279 /* TransferHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryViewController.swift; sourceTree = ""; }; + FB60B43F215E611900B59279 /* TransferHistoryHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryHeaderView.swift; sourceTree = ""; }; + FB60B440215E611900B59279 /* TransferHistoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryCell.swift; sourceTree = ""; }; + FB60B442215E611900B59279 /* TransferHistoryInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryInteractor.swift; sourceTree = ""; }; + FB60B444215E611900B59279 /* TransferHistoryTableModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferHistoryTableModel.swift; sourceTree = ""; }; + FB60B47E215E618200B59279 /* WalletServiceResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletServiceResult.swift; sourceTree = ""; }; + FB60B47F215E618200B59279 /* WalletServiceWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletServiceWallet.swift; sourceTree = ""; }; + FB60B48C215E671300B59279 /* WalletBalancesWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletBalancesWallet.swift; sourceTree = ""; }; + FB60B48D215E671300B59279 /* WalletBalancesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletBalancesViewModel.swift; sourceTree = ""; }; + FB60B48E215E671300B59279 /* WalletBalancesOutput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletBalancesOutput.swift; sourceTree = ""; }; + FB60B493215E671D00B59279 /* PaymentViewControllerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentViewControllerModel.swift; sourceTree = ""; }; + FB60B494215E671D00B59279 /* PaymentTableCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentTableCellModel.swift; sourceTree = ""; }; + FB60B495215E671D00B59279 /* PaymentModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentModel.swift; sourceTree = ""; }; + FB60B4A7215E770300B59279 /* SeedVerificationWalletPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedVerificationWalletPresenter.swift; sourceTree = ""; }; + FB60B4A9215E770300B59279 /* SeedVerificationWalletWireFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedVerificationWalletWireFrame.swift; sourceTree = ""; }; + FB60B4AA215E770300B59279 /* SeedVerificationWalletProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedVerificationWalletProtocols.swift; sourceTree = ""; }; + FB60B4AC215E770300B59279 /* SeedVerificationWalletCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedVerificationWalletCollectionViewCell.swift; sourceTree = ""; }; + FB60B4AD215E770300B59279 /* SeedVerificationWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedVerificationWalletViewController.swift; sourceTree = ""; }; + FB60B4AF215E770300B59279 /* SeedVerificationWalletInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedVerificationWalletInteractor.swift; sourceTree = ""; }; + FB60B4B1215E770300B59279 /* SeedVerificationWalletInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedVerificationWalletInput.swift; sourceTree = ""; }; + FB60B4B4215E770300B59279 /* SeedBackupWalletPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletPresenter.swift; sourceTree = ""; }; + FB60B4B5215E770300B59279 /* SeedBackupWalletProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletProtocols.swift; sourceTree = ""; }; + FB60B4B7215E770300B59279 /* SeedBackupWalletWireFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletWireFrame.swift; sourceTree = ""; }; + FB60B4B9215E770300B59279 /* SeedBackupWalletCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletCollectionViewCell.swift; sourceTree = ""; }; + FB60B4BA215E770300B59279 /* SeedBackupWalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletViewController.swift; sourceTree = ""; }; + FB60B4BC215E770300B59279 /* SeedBackupWalletInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletInteractor.swift; sourceTree = ""; }; + FB60B4BE215E770300B59279 /* SeedBackupWalletInputParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletInputParams.swift; sourceTree = ""; }; + FB60B4BF215E770300B59279 /* SeedBackupWalletOutputParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeedBackupWalletOutputParams.swift; sourceTree = ""; }; + FB60B4CF215E780800B59279 /* MessageTransferView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTransferView.swift; sourceTree = ""; }; + FB60B4D1215E794500B59279 /* TransferCoinstoContactsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferCoinstoContactsView.swift; sourceTree = ""; }; FB61D65E214FF91F00CB2A1F /* Colors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Colors.json; sourceTree = ""; }; FB61D661214FF96200CB2A1F /* ColorsConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorsConstants.swift; sourceTree = ""; }; FB816EB920B59E0400093DCD /* HistoryRequestModelFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryRequestModelFactory.swift; sourceTree = ""; }; @@ -3991,7 +4102,6 @@ FBCE83CD20E52351003B7558 /* PaymentInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentInteractor.swift; sourceTree = ""; }; FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTServiceWallet.swift; sourceTree = ""; }; FBCE83D620E523A8003B7558 /* WalletMQTTModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletMQTTModel.swift; sourceTree = ""; }; - FBCE83DB20E52437003B7558 /* MessagePaymentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagePaymentView.swift; sourceTree = ""; }; FBCE83DE20E52496003B7558 /* ContactServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactServices.swift; sourceTree = ""; }; FBCE83DF20E52496003B7558 /* ProfileServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileServices.swift; sourceTree = ""; }; FBCE83FA20E525A5003B7558 /* HTTPResponseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPResponseResult.swift; sourceTree = ""; }; @@ -5338,6 +5448,7 @@ 26DCB2362064B909001EF0AB /* View */ = { isa = PBXGroup; children = ( + FB60B4D1215E794500B59279 /* TransferCoinstoContactsView.swift */, 26DCB2402064B9B1001EF0AB /* InviteFriendHeaderView.swift */, 26DCB23F2064B9B0001EF0AB /* InviteFriendHeaderViewLayout.swift */, 26DCB23A2064B958001EF0AB /* ViewController */, @@ -5671,6 +5782,7 @@ 3A768E1C1ECD152300108F7C /* Services */ = { isa = PBXGroup; children = ( + FB60B405215E600500B59279 /* WalletFundingNetworkService */, 4B7C73E7215A5508007924DB /* Debug */, 8509FC832158F7B400734D93 /* Files */, 4BE2C5E42142EB5A00A73DD9 /* NynjaCalls */, @@ -6051,10 +6163,9 @@ 49E75E252CE2F3C96A626230 /* Modules */ = { isa = PBXGroup; children = ( + FB60B410215E608200B59279 /* Wallet Flows */, 4B749F0E214FEFC8002F3A33 /* Auth */, 260531122127407A002E1CF1 /* LogOutput */, - FBCE83C320E52351003B7558 /* Payment */, - FBF0E38320E5232E00B6FB59 /* WalletBalances */, 855AC52C208E435700DC2335 /* Stickers */, 2603136720A0A4B9009AC66D /* LanguageSettings */, F105C690209F71BE0091786A /* Flows */, @@ -9054,7 +9165,7 @@ A418DA2F20ED08EA00FE780B /* Message */ = { isa = PBXGroup; children = ( - FBCE83DB20E52437003B7558 /* MessagePaymentView.swift */, + FB60B4CF215E780800B59279 /* MessageTransferView.swift */, 2603068C20FFF9CA00C10DD9 /* MessageCallView.swift */, A45F10D220B4218D00F45004 /* MessageContactView.swift */, A45F10D320B4218D00F45004 /* MessageImageView.swift */, @@ -12645,6 +12756,393 @@ path = ImagePreviewTransitionController; sourceTree = ""; }; + FB60B405215E600500B59279 /* WalletFundingNetworkService */ = { + isa = PBXGroup; + children = ( + FB60B406215E600500B59279 /* WalletFundingNetworkService.swift */, + FB60B407215E600500B59279 /* WalletFundingNetworkRouter.swift */, + FB60B408215E600500B59279 /* Entities */, + ); + path = WalletFundingNetworkService; + sourceTree = ""; + }; + FB60B408215E600500B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B409215E600500B59279 /* WalletFundingNetworkResponse.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B410215E608200B59279 /* Wallet Flows */ = { + isa = PBXGroup; + children = ( + FB60B4B2215E770300B59279 /* SeedBackup */, + FB60B4A5215E770300B59279 /* SeedVerification */, + FB60B427215E611900B59279 /* CreateWallet */, + FB60B41D215E611900B59279 /* TransferDetails */, + FB60B435215E611900B59279 /* TransferHistory */, + FB60B411215E611900B59279 /* WalletDetails */, + FBCE83C320E52351003B7558 /* Payment */, + FBF0E38320E5232E00B6FB59 /* WalletBalances */, + ); + path = "Wallet Flows"; + sourceTree = ""; + }; + FB60B411215E611900B59279 /* WalletDetails */ = { + isa = PBXGroup; + children = ( + FB60B412215E611900B59279 /* Presenter */, + FB60B414215E611900B59279 /* WireFrame */, + FB60B416215E611900B59279 /* View */, + FB60B418215E611900B59279 /* WalletDetailsProtocols.swift */, + FB60B419215E611900B59279 /* Interactor */, + FB60B41B215E611900B59279 /* Entities */, + ); + path = WalletDetails; + sourceTree = ""; + }; + FB60B412215E611900B59279 /* Presenter */ = { + isa = PBXGroup; + children = ( + FB60B413215E611900B59279 /* WalletDetailsPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + FB60B414215E611900B59279 /* WireFrame */ = { + isa = PBXGroup; + children = ( + FB60B415215E611900B59279 /* WalletDetailsWireFrame.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + FB60B416215E611900B59279 /* View */ = { + isa = PBXGroup; + children = ( + FB60B417215E611900B59279 /* WalletDetailsViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + FB60B419215E611900B59279 /* Interactor */ = { + isa = PBXGroup; + children = ( + FB60B41A215E611900B59279 /* WalletDetailsInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + FB60B41B215E611900B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B41C215E611900B59279 /* WalletDetailsViewInput.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B41D215E611900B59279 /* TransferDetails */ = { + isa = PBXGroup; + children = ( + FB60B41E215E611900B59279 /* Presenter */, + FB60B420215E611900B59279 /* WireFrame */, + FB60B422215E611900B59279 /* View */, + FB60B424215E611900B59279 /* Interactor */, + FB60B426215E611900B59279 /* TransferDetailsProtocols.swift */, + ); + path = TransferDetails; + sourceTree = ""; + }; + FB60B41E215E611900B59279 /* Presenter */ = { + isa = PBXGroup; + children = ( + FB60B41F215E611900B59279 /* TransferDetailsPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + FB60B420215E611900B59279 /* WireFrame */ = { + isa = PBXGroup; + children = ( + FB60B421215E611900B59279 /* TransferDetailsWireFrame.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + FB60B422215E611900B59279 /* View */ = { + isa = PBXGroup; + children = ( + FB60B423215E611900B59279 /* TransferDetailsViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + FB60B424215E611900B59279 /* Interactor */ = { + isa = PBXGroup; + children = ( + FB60B425215E611900B59279 /* TransferDetailsInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + FB60B427215E611900B59279 /* CreateWallet */ = { + isa = PBXGroup; + children = ( + FB60B428215E611900B59279 /* CreateWalletProtocols.swift */, + FB60B429215E611900B59279 /* Presenter */, + FB60B42B215E611900B59279 /* WireFrame */, + FB60B42D215E611900B59279 /* View */, + FB60B42F215E611900B59279 /* Interactor */, + FB60B431215E611900B59279 /* Entities */, + ); + path = CreateWallet; + sourceTree = ""; + }; + FB60B429215E611900B59279 /* Presenter */ = { + isa = PBXGroup; + children = ( + FB60B42A215E611900B59279 /* CreateWalletPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + FB60B42B215E611900B59279 /* WireFrame */ = { + isa = PBXGroup; + children = ( + FB60B42C215E611900B59279 /* CreateWalletWireFrame.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + FB60B42D215E611900B59279 /* View */ = { + isa = PBXGroup; + children = ( + FB60B42E215E611900B59279 /* CreateWalletViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + FB60B42F215E611900B59279 /* Interactor */ = { + isa = PBXGroup; + children = ( + FB60B430215E611900B59279 /* CreateWalletInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + FB60B431215E611900B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B432215E611900B59279 /* CreateWalletState.swift */, + FB60B433215E611900B59279 /* CreateWalletValidationViewModel.swift */, + FB60B434215E611900B59279 /* CreateWalletParams.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B435215E611900B59279 /* TransferHistory */ = { + isa = PBXGroup; + children = ( + FB60B436215E611900B59279 /* TransferHistoryProtocols.swift */, + FB60B437215E611900B59279 /* Presenter */, + FB60B439215E611900B59279 /* WireFrame */, + FB60B43B215E611900B59279 /* View */, + FB60B441215E611900B59279 /* Interactor */, + FB60B443215E611900B59279 /* Entities */, + ); + path = TransferHistory; + sourceTree = ""; + }; + FB60B437215E611900B59279 /* Presenter */ = { + isa = PBXGroup; + children = ( + FB60B438215E611900B59279 /* TransferHistoryPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + FB60B439215E611900B59279 /* WireFrame */ = { + isa = PBXGroup; + children = ( + FB60B43A215E611900B59279 /* TransferHistoryWireFrame.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + FB60B43B215E611900B59279 /* View */ = { + isa = PBXGroup; + children = ( + FB60B43C215E611900B59279 /* TableView */, + FB60B43E215E611900B59279 /* TransferHistoryViewController.swift */, + FB60B43F215E611900B59279 /* TransferHistoryHeaderView.swift */, + FB60B440215E611900B59279 /* TransferHistoryCell.swift */, + ); + path = View; + sourceTree = ""; + }; + FB60B43C215E611900B59279 /* TableView */ = { + isa = PBXGroup; + children = ( + FB60B43D215E611900B59279 /* TransferHistoryTableDataSource.swift */, + ); + path = TableView; + sourceTree = ""; + }; + FB60B441215E611900B59279 /* Interactor */ = { + isa = PBXGroup; + children = ( + FB60B442215E611900B59279 /* TransferHistoryInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + FB60B443215E611900B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B444215E611900B59279 /* TransferHistoryTableModel.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B47D215E618200B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B47E215E618200B59279 /* WalletServiceResult.swift */, + FB60B47F215E618200B59279 /* WalletServiceWallet.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B48B215E671300B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B48C215E671300B59279 /* WalletBalancesWallet.swift */, + FB60B48D215E671300B59279 /* WalletBalancesViewModel.swift */, + FB60B48E215E671300B59279 /* WalletBalancesOutput.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B492215E671D00B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B493215E671D00B59279 /* PaymentViewControllerModel.swift */, + FB60B494215E671D00B59279 /* PaymentTableCellModel.swift */, + FB60B495215E671D00B59279 /* PaymentModel.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B4A5215E770300B59279 /* SeedVerification */ = { + isa = PBXGroup; + children = ( + FB60B4A6215E770300B59279 /* Presenter */, + FB60B4A8215E770300B59279 /* WireFrame */, + FB60B4AA215E770300B59279 /* SeedVerificationWalletProtocols.swift */, + FB60B4AB215E770300B59279 /* View */, + FB60B4AE215E770300B59279 /* Interactor */, + FB60B4B0215E770300B59279 /* Entities */, + ); + path = SeedVerification; + sourceTree = ""; + }; + FB60B4A6215E770300B59279 /* Presenter */ = { + isa = PBXGroup; + children = ( + FB60B4A7215E770300B59279 /* SeedVerificationWalletPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + FB60B4A8215E770300B59279 /* WireFrame */ = { + isa = PBXGroup; + children = ( + FB60B4A9215E770300B59279 /* SeedVerificationWalletWireFrame.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + FB60B4AB215E770300B59279 /* View */ = { + isa = PBXGroup; + children = ( + FB60B4AC215E770300B59279 /* SeedVerificationWalletCollectionViewCell.swift */, + FB60B4AD215E770300B59279 /* SeedVerificationWalletViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + FB60B4AE215E770300B59279 /* Interactor */ = { + isa = PBXGroup; + children = ( + FB60B4AF215E770300B59279 /* SeedVerificationWalletInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + FB60B4B0215E770300B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B4B1215E770300B59279 /* SeedVerificationWalletInput.swift */, + ); + path = Entities; + sourceTree = ""; + }; + FB60B4B2215E770300B59279 /* SeedBackup */ = { + isa = PBXGroup; + children = ( + FB60B4B3215E770300B59279 /* Presenter */, + FB60B4B5215E770300B59279 /* SeedBackupWalletProtocols.swift */, + FB60B4B6215E770300B59279 /* WireFrame */, + FB60B4B8215E770300B59279 /* View */, + FB60B4BB215E770300B59279 /* Interactor */, + FB60B4BD215E770300B59279 /* Entities */, + ); + path = SeedBackup; + sourceTree = ""; + }; + FB60B4B3215E770300B59279 /* Presenter */ = { + isa = PBXGroup; + children = ( + FB60B4B4215E770300B59279 /* SeedBackupWalletPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + FB60B4B6215E770300B59279 /* WireFrame */ = { + isa = PBXGroup; + children = ( + FB60B4B7215E770300B59279 /* SeedBackupWalletWireFrame.swift */, + ); + path = WireFrame; + sourceTree = ""; + }; + FB60B4B8215E770300B59279 /* View */ = { + isa = PBXGroup; + children = ( + FB60B4B9215E770300B59279 /* SeedBackupWalletCollectionViewCell.swift */, + FB60B4BA215E770300B59279 /* SeedBackupWalletViewController.swift */, + ); + path = View; + sourceTree = ""; + }; + FB60B4BB215E770300B59279 /* Interactor */ = { + isa = PBXGroup; + children = ( + FB60B4BC215E770300B59279 /* SeedBackupWalletInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + FB60B4BD215E770300B59279 /* Entities */ = { + isa = PBXGroup; + children = ( + FB60B4BE215E770300B59279 /* SeedBackupWalletInputParams.swift */, + FB60B4BF215E770300B59279 /* SeedBackupWalletOutputParams.swift */, + ); + path = Entities; + sourceTree = ""; + }; FB816EEC20B5B30E00093DCD /* Services */ = { isa = PBXGroup; children = ( @@ -12716,6 +13214,7 @@ FBCE83C320E52351003B7558 /* Payment */ = { isa = PBXGroup; children = ( + FB60B492215E671D00B59279 /* Entities */, FBCE83C420E52351003B7558 /* Presenter */, FBCE83C620E52351003B7558 /* WireFrame */, FBCE83C820E52351003B7558 /* View */, @@ -12856,6 +13355,7 @@ FBF0E38320E5232E00B6FB59 /* WalletBalances */ = { isa = PBXGroup; children = ( + FB60B48B215E671300B59279 /* Entities */, FB98194820F6478700C5FB86 /* WireFrame */, FB98194720F6477600C5FB86 /* View */, FB98194620F6476E00C5FB86 /* Presenter */, @@ -12894,6 +13394,7 @@ FE2D7CCB211C71AD00520D78 /* WalletService */ = { isa = PBXGroup; children = ( + FB60B47D215E618200B59279 /* Entities */, FE2D7CCC211C71AD00520D78 /* WalletService.swift */, ); name = WalletService; @@ -13244,7 +13745,7 @@ "${BUILT_PRODUCTS_DIR}/MDFTextAccessibility/MDFTextAccessibility.framework", "${BUILT_PRODUCTS_DIR}/MaterialComponents/MaterialComponents.framework", "${BUILT_PRODUCTS_DIR}/MulticastDelegateSwift/MulticastDelegateSwift.framework", - "${PODS_ROOT}/NynjaSDK/NynjaSDK.framework", + "${PODS_ROOT}/NynjaSDK-Wallet/NynjaSDK.framework", "${BUILT_PRODUCTS_DIR}/QRCode/QRCode.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", @@ -13773,6 +14274,7 @@ 262D43872033417F002F1E45 /* FriendExtansion+BERT.swift in Sources */, FE58F9B1208F00FE004AFDD3 /* MessageEditActionTable.swift in Sources */, F105C6A0209F71BF0091786A /* CameraInteractor.swift in Sources */, + FB60B498215E671D00B59279 /* PaymentModel.swift in Sources */, 4B4266BA204D898900194BC1 /* ForwardSelectorDisplayMode.swift in Sources */, 8572C3B92092364C00E4840C /* StickerPackageDataSource.swift in Sources */, A42D51B6206A361400EEB952 /* iter.swift in Sources */, @@ -13820,6 +14322,7 @@ 8580BAF420BD9B8000239D9D /* Range+Extension.swift in Sources */, A42D52C6206A53AA00EEB952 /* Feature_Spec.swift in Sources */, F10AFEB620F7B1B000C7CE83 /* WheelImageFullItemPreview.swift in Sources */, + FB60B465215E611A00B59279 /* CreateWalletState.swift in Sources */, A45F112A20B4218D00F45004 /* BubbleInjectible.swift in Sources */, 4B1D7E14202A0A0200703228 /* GroupMode.swift in Sources */, 3A27B0A71EF307A900B4B3CB /* DeleteUserModel.swift in Sources */, @@ -13876,6 +14379,7 @@ 85B750A120334A2B00AD6013 /* ForwardTableViewCell.swift in Sources */, 00E8646D204D788100844FF1 /* LanguageSettingsCell.swift in Sources */, 3AF4A3DA1EFD7DFA0059B405 /* StringExtensions.swift in Sources */, + FB60B40C215E600500B59279 /* WalletFundingNetworkRouter.swift in Sources */, E7EED2361F740CEA005DAE20 /* NewContactItem.swift in Sources */, 26DCB2502064BA3D001EF0AB /* ContactsPresenter.swift in Sources */, A42D51B4206A361400EEB952 /* container.swift in Sources */, @@ -13901,6 +14405,7 @@ F1607B2C20B2DE7300BDF60A /* CameraQRPreviewViewController.swift in Sources */, 26AD28371FFB0AE3009E4580 /* StorageSubscriber.swift in Sources */, A49E1BCD20A9A6970074DFD3 /* BaseChatModel.swift in Sources */, + FB60B4CC215E770400B59279 /* SeedBackupWalletInteractor.swift in Sources */, 3A62B7D81F4CB9D100F45B51 /* BaseMQTTModel.swift in Sources */, 3A1DC73F1EF15B65006A8E9F /* IoHandler.swift in Sources */, E77FBDDD1FFE828400BDB255 /* AVURLAsset+Duration.swift in Sources */, @@ -13935,6 +14440,7 @@ F11DF06C20BEF43A00F3E005 /* ResourceManager.swift in Sources */, F117871920ACF018007A9A1B /* CameraSettingsInteractor.swift in Sources */, 26FA420A2017ADF000E6F6EC /* StarMessageCell.swift in Sources */, + FB60B497215E671D00B59279 /* PaymentTableCellModel.swift in Sources */, C9C6952620232B0200A57297 /* SortableObject.swift in Sources */, A42D51B9206A361400EEB952 /* serviceTask.swift in Sources */, E751E0051F73A70F00FEF533 /* MainItem.swift in Sources */, @@ -13964,12 +14470,14 @@ A42D52CA206A53AB00EEB952 /* muc_Spec.swift in Sources */, 850C301D204DA87A00DB26C2 /* PrivacyListProtocols.swift in Sources */, B77C11DF2109242200CCB42E /* AssigningInterpreterWireFrame.swift in Sources */, + FB60B496215E671D00B59279 /* PaymentViewControllerModel.swift in Sources */, 8520040D20D513B8007C0036 /* OpponentMessageStickerRepliedView.swift in Sources */, A458FAC420EBA58A0075D55E /* MuteChatService.swift in Sources */, 2661D12F1F373D1700F3E125 /* BorderView.swift in Sources */, A47785A420D286680053E0D2 /* ChannelChatItemsFactory.swift in Sources */, F117870D20ACF018007A9A1B /* CameraQualitySettingsPresenter.swift in Sources */, 26EAA2CB20D2497F005697CB /* TranslationAutoView.swift in Sources */, + FB60B4C9215E770400B59279 /* SeedBackupWalletWireFrame.swift in Sources */, 85D669EA20BD95FA00FBD803 /* MessagePresenter+MentionUnreadCounter.swift in Sources */, 854A4B312080D6C400759152 /* CellWithImageCellModel.swift in Sources */, FBCE841020E525A6003B7558 /* HTTPHeader+Authorization.swift in Sources */, @@ -14031,6 +14539,7 @@ 4BB0EFBD2151347900704136 /* AlertManager.swift in Sources */, D30EB73829E48C0B1C1FD1C9 /* LoginViewController.swift in Sources */, A42D51BA206A361400EEB952 /* io.swift in Sources */, + FB60B4CE215E770400B59279 /* SeedBackupWalletOutputParams.swift in Sources */, 3A1EB9A51F3A848A00658E93 /* HistoryHandler.swift in Sources */, 850FC5F42032F4CE00832D87 /* ForwardTargets.swift in Sources */, 85788C422044237B003600C9 /* BuildNumberViewController.swift in Sources */, @@ -14050,6 +14559,7 @@ 0062D9472062EC4100B915AC /* InviteFriendsViewController.swift in Sources */, 4B2D063C202E1A1500010A0C /* ContactsExpandedItemsFactory.swift in Sources */, 85D66A2120BD970400FBD803 /* BBCodeEntity.swift in Sources */, + FB60B45D215E611A00B59279 /* CreateWalletPresenter.swift in Sources */, B74BB00221076AFA0049CD27 /* CircleMenuFactory.swift in Sources */, 4B8996EC204EF35200DCB183 /* MessageActionDAO.swift in Sources */, E791178A1F97874D00462D68 /* GradientView.swift in Sources */, @@ -14071,11 +14581,13 @@ E73483211F9F78DC0090A4DB /* ProfileSectionFooterView.swift in Sources */, 3AE0A84B1F20321A008A04F3 /* Wheel.swift in Sources */, 00E4A65F201A287100CEC61F /* MapSearchDS.swift in Sources */, + FB60B473215E611A00B59279 /* TransferHistoryViewController.swift in Sources */, 2603139D20A0A4BA009AC66D /* ChatLanguageSettingsProtocols.swift in Sources */, 26142B1120472ECD004E5FE4 /* MessageLinkTable.swift in Sources */, A4679BAC20B2DD100021FE9C /* SubscribersSelectorViewController.swift in Sources */, 8514F17320EA219E00883513 /* ContextMenuConfiguration+Favorites.swift in Sources */, A42D52BF206A53AA00EEB952 /* push_Spec.swift in Sources */, + FB60B461215E611A00B59279 /* CreateWalletViewController.swift in Sources */, 3A1D03051F0BD93A005F5F18 /* LocationService.swift in Sources */, 26441A121F9FBF9300E724B5 /* MainViewController+NavigateProtocol.swift in Sources */, A43B25C020AB1E9600FF8107 /* NumericInputValidator.swift in Sources */, @@ -14131,7 +14643,6 @@ 850FC5F82032F63000832D87 /* ForwardSelectorInteractor.swift in Sources */, C9B8BEF9204DDCBC0018748C /* CheckmarkCell.swift in Sources */, A42D52D2206A53AB00EEB952 /* userTask_Spec.swift in Sources */, - FBCE83DC20E52437003B7558 /* MessagePaymentView.swift in Sources */, A45F113C20B4218D00F45004 /* MessageInteractor.swift in Sources */, A42D52E0206A53AB00EEB952 /* Tag_Spec.swift in Sources */, 853E595720D70F9A007799B9 /* DBStickerPack.swift in Sources */, @@ -14157,6 +14668,7 @@ A45F112E20B4218D00F45004 /* MessageContentProtocol.swift in Sources */, 0008E9132032D5AC003E316E /* MQTTServiceSchedule.swift in Sources */, A432CF1A20B4347D00993AFB /* MaterialTextInput.swift in Sources */, + FB60B4C8215E770400B59279 /* SeedBackupWalletProtocols.swift in Sources */, B77C11E62109254800CCB42E /* InterpretationTypePresenter.swift in Sources */, B750EF062046D7C700A99F9C /* SpeedStringRepresentable.swift in Sources */, 8580BAE220BD99D200239D9D /* InputContent.swift in Sources */, @@ -14199,7 +14711,9 @@ E74FD69F1FC5DEAA00656611 /* DBContact.swift in Sources */, 0062D93C2062EC4100B915AC /* InviteFriendsPresenter.swift in Sources */, 0008E9052031E642003E316E /* UIEdgeInsets+Adjust.swift in Sources */, + FB60B455215E611A00B59279 /* TransferDetailsViewController.swift in Sources */, A4F3DAB5208494E300FF71C7 /* UIViewController+SafeArea.swift in Sources */, + FB60B477215E611A00B59279 /* TransferHistoryCell.swift in Sources */, 4B8996CA204ECEA700DCB183 /* ContactDAOProtocol.swift in Sources */, 85D66A0520BD963C00FBD803 /* MessagePayloadParser.swift in Sources */, 265EB72020A86A5400C1483E /* LangExtended.swift in Sources */, @@ -14233,6 +14747,7 @@ 8E6C4BE61FF83D93009C8374 /* IntExtensions.swift in Sources */, E785EF2A1FB9D99400F0C689 /* PinView.swift in Sources */, A42D51D0206A361400EEB952 /* Star.swift in Sources */, + FB60B449215E611A00B59279 /* WalletDetailsViewController.swift in Sources */, 85C16C3E20D2794500EDB77E /* BubbleImageSizeCalculatable.swift in Sources */, A43B25D620AB1EE400FF8107 /* NewChannelPresenter.swift in Sources */, E7598F691FA1D8B90082FBE7 /* ProfileScheduledMesssageCellLayout.swift in Sources */, @@ -14261,6 +14776,7 @@ 855AC53F208E45AA00DC2335 /* StickerCollectionViewCell.swift in Sources */, 850A0C672046B65D004F79AD /* WCItemsFactoryDecorator.swift in Sources */, A402A1CC20DE694A005BFA20 /* PartialCheckableButton.swift in Sources */, + FB60B482215E618300B59279 /* WalletServiceWallet.swift in Sources */, 4B736D4920238FA40028F2CB /* ThumbnailGenerator.swift in Sources */, A42D52B5206A53AA00EEB952 /* timeoutEvent_Spec.swift in Sources */, 8EC767DC2007850400655F80 /* GroupImagesCell.swift in Sources */, @@ -14314,6 +14830,7 @@ 267BE28E1FDE9FCC00C47E18 /* SettingsGroupWireFrame.swift in Sources */, 85BDD2B821465EFA00695DE5 /* ScrollDirection.swift in Sources */, A4679BA620B2DD0F0021FE9C /* SubscribersSelectorWireFrame.swift in Sources */, + FB60B469215E611A00B59279 /* CreateWalletParams.swift in Sources */, 2648C41A2069B52100863614 /* ChangeNumberViewLayout.swift in Sources */, 8514D52420EE48A30002378A /* NynjaContextMenuItemsFactory+Messages.swift in Sources */, 269666181FB57963009E41C1 /* RoomHandler.swift in Sources */, @@ -14322,6 +14839,7 @@ A415132020DBD58900C2C01F /* Link.swift in Sources */, 852DF263203720E600A4F8B6 /* FileIcons.swift in Sources */, A43B25DB20AB1EE400FF8107 /* NewChannelInteractor.swift in Sources */, + FB60B45F215E611A00B59279 /* CreateWalletWireFrame.swift in Sources */, FBCE840F20E525A6003B7558 /* HTTPParameters.swift in Sources */, 850A0C6520469AED004F79AD /* UserSettingsRespondable.swift in Sources */, E7598F6C1FA1D8B90082FBE7 /* ProfileContactCellLayout.swift in Sources */, @@ -14330,6 +14848,7 @@ E757B53D1FE9225C00467BA2 /* TypingExtension.swift in Sources */, C940514C204C7FAF00D72B04 /* DataAndStorageInteractor.swift in Sources */, F1A9FA3590CC1F834B727955 /* AddContactPresenter.swift in Sources */, + FB60B459215E611A00B59279 /* TransferDetailsProtocols.swift in Sources */, 6DD72F601F1547AC008CFF83 /* GCD.swift in Sources */, A49CC1D820E4AB2C00879D41 /* DisplayModeConfigFactory.swift in Sources */, 859C429F2056829300AE3797 /* NotificationSettings.swift in Sources */, @@ -14354,8 +14873,10 @@ F11786CD20A8E4FD007A9A1B /* CameraVideoPreviewInteractor.swift in Sources */, A4679BBA20B305360021FE9C /* LinkValidator.swift in Sources */, 4BEE89D69CACB85ABEE9046F /* QRCodeGeneratorPresenter.swift in Sources */, + FB60B4C4215E770400B59279 /* SeedVerificationWalletViewController.swift in Sources */, 2605311B212740FD002E1CF1 /* LogOutputProtocols.swift in Sources */, FBCE841420E525A6003B7558 /* NetworkService.swift in Sources */, + FB60B4CD215E770400B59279 /* SeedBackupWalletInputParams.swift in Sources */, A409B1CF2108D48E0051C20B /* QueryFactory.swift in Sources */, A42D52B7206A53AA00EEB952 /* reader_Spec.swift in Sources */, F119E66A20D24B960043A532 /* MultiplePreviewProtocols.swift in Sources */, @@ -14415,6 +14936,7 @@ B7EF8ED9210C71E800E0E981 /* InterpretationType.swift in Sources */, 6D5157D21F30B822002A27DB /* MicrophoneView.swift in Sources */, 260313AF20A0A50D009AC66D /* TranslationService.swift in Sources */, + FB60B4C6215E770400B59279 /* SeedVerificationWalletInput.swift in Sources */, A42D51AD206A361400EEB952 /* cur.swift in Sources */, 8504DEAB206937A2006722AC /* MediaFullWheelItemView.swift in Sources */, BF20ED73252DE6954B6CDCA8 /* QRCodeReaderViewController.swift in Sources */, @@ -14451,6 +14973,7 @@ 853E595B20D71E6C007799B9 /* StickerPack+DB.swift in Sources */, A4CE80C720C95EE000400713 /* CollectionState.swift in Sources */, 65AC1F6564EEFA0439F5C236 /* QRCodeReaderWireframe.swift in Sources */, + FB60B4CA215E770400B59279 /* SeedBackupWalletCollectionViewCell.swift in Sources */, 039B595B084FE6336504E071 /* ProfileProtocols.swift in Sources */, F11786E020A9F11D007A9A1B /* UploadOperation.swift in Sources */, A42D52D8206A53AB00EEB952 /* task_Spec.swift in Sources */, @@ -14460,6 +14983,7 @@ E74EC9ED1FC2DA6E007268E6 /* RoomTable.swift in Sources */, A42D52C8206A53AB00EEB952 /* Auth_Spec.swift in Sources */, 267BE90920693F4400153FB8 /* ProfileDAO.swift in Sources */, + FB60B447215E611A00B59279 /* WalletDetailsWireFrame.swift in Sources */, E77D58A21F98C58A00FBE926 /* LabelExtensions.swift in Sources */, A415132620DBE3A800C2C01F /* DBLink.swift in Sources */, A45F111C20B4218D00F45004 /* MessageFileView.swift in Sources */, @@ -14475,6 +14999,7 @@ E76462891FCD64790091FC2E /* DBModelProtocol.swift in Sources */, A418DA3120ED092F00FE780B /* InfoChannelView.swift in Sources */, E71AB31E1F70188C00A0CF5A /* WheelItemViewFactory.swift in Sources */, + FB60B44B215E611A00B59279 /* WalletDetailsProtocols.swift in Sources */, 8580BACE20BD98CF00239D9D /* UpdateResult.swift in Sources */, A42D51C4206A361400EEB952 /* Task.swift in Sources */, E701A27D1FB33E1700D995C3 /* ParticipantsActionsDelegate.swift in Sources */, @@ -14496,8 +15021,10 @@ 8557988820932401007050B8 /* StickerStaticMenuActionCellModel.swift in Sources */, 267BE2921FDEA0C100C47E18 /* SettingsGroupInteractor.swift in Sources */, A43B25AB20AB1DFA00FF8107 /* ALTextView.swift in Sources */, + FB60B44D215E611A00B59279 /* WalletDetailsInteractor.swift in Sources */, 4BDC7E61203492CA00BCD381 /* TopSwipable.swift in Sources */, A4626EB320D96FAE000F37EE /* TopLevelInfo.swift in Sources */, + FB60B490215E671400B59279 /* WalletBalancesViewModel.swift in Sources */, 267BE90C2069405200153FB8 /* StarMessageDAOProtocol.swift in Sources */, 8E55172E200D095B00C12B5D /* UserGroupRulesVC.swift in Sources */, A4B544EA20EFB1A800EB7B0F /* errors_Spec.swift in Sources */, @@ -14521,6 +15048,7 @@ A45F112320B4218D00F45004 /* MessageVoiceView.swift in Sources */, A44B4D5520CE9BDF00CA700A /* SwitchCellViewModel.swift in Sources */, B74BAFFB21076AFA0049CD27 /* CircleMenu.swift in Sources */, + FB60B4D0215E780900B59279 /* MessageTransferView.swift in Sources */, F117872520ACF2DB007A9A1B /* QualityName.swift in Sources */, A45F111B20B4218D00F45004 /* MessagePlaceView.swift in Sources */, A42D52C4206A53AA00EEB952 /* error2_Spec.swift in Sources */, @@ -14544,6 +15072,7 @@ A42D51AC206A361400EEB952 /* Auth.swift in Sources */, A42D52CD206A53AB00EEB952 /* chain_Spec.swift in Sources */, 8580BACA20BD983400239D9D /* MentionTransitionProtocol.swift in Sources */, + FB60B4C0215E770400B59279 /* SeedVerificationWalletPresenter.swift in Sources */, A44B4D5B20CE9BDF00CA700A /* ImageCell.swift in Sources */, 00E9825E205FDB1A008BF03D /* AuthExtension.swift in Sources */, A45F116020B422AF00F45004 /* Message+DB.swift in Sources */, @@ -14553,13 +15082,17 @@ 2648C41B2069B52100863614 /* ChangeNumberView.swift in Sources */, 2648C40E2069B52100863614 /* ChangeNumberStep1Interactor.swift in Sources */, 85057966206D17AB00565C60 /* ImagePickerHandler.swift in Sources */, + FB60B457215E611A00B59279 /* TransferDetailsInteractor.swift in Sources */, + FB60B46D215E611A00B59279 /* TransferHistoryPresenter.swift in Sources */, 6D5157D01F30B36A002A27DB /* ChatView.swift in Sources */, 8572C3BB2092366100E4840C /* StickerCollectionDataSource.swift in Sources */, E735853D1F6C2705003354B5 /* Geometry.swift in Sources */, 85B0013421272694000C89FE /* MessageInteractor+History.swift in Sources */, F119E67720D27E990043A532 /* ImagePreviewCVCell.swift in Sources */, 260313A720A0A4BA009AC66D /* ChatLanguageSettingsTableDataSource.swift in Sources */, + FB60B471215E611A00B59279 /* TransferHistoryTableDataSource.swift in Sources */, 260313A520A0A4BA009AC66D /* BaseCell.swift in Sources */, + FB60B45B215E611A00B59279 /* CreateWalletProtocols.swift in Sources */, 00EDCA0D202B7243000928D4 /* TimeZoneSelectorDS.swift in Sources */, 32868DDB1F31CB500028B260 /* ChatsListViewController.swift in Sources */, E709383D1FBEE176006CCDC6 /* ServiceTable.swift in Sources */, @@ -14701,6 +15234,7 @@ E764919B1F7A5485001E741C /* MainWheelContainerDelegate.swift in Sources */, A4330A6A2109EA850060BD93 /* DatabaseManager.swift in Sources */, 8E6C4BDE1FF40B97009C8374 /* GroupFilesCell.swift in Sources */, + FB60B40A215E600500B59279 /* WalletFundingNetworkService.swift in Sources */, 26DCB24E2064B9DC001EF0AB /* ContactsTableDS.swift in Sources */, A45F114820B421AB00F45004 /* Contact+BaseChatModel.swift in Sources */, 6CED2C4CE125011A3A731D62 /* AddContactViaPhoneInteractor.swift in Sources */, @@ -14744,6 +15278,7 @@ 266F04CB2015050400B97A83 /* DBStarMessage.swift in Sources */, 1D31D13E6E53E71F8279C55C /* HistoryProtocols.swift in Sources */, A4CE80C020C9318700400713 /* EmptyStateTableViewDS.swift in Sources */, + FB60B48F215E671400B59279 /* WalletBalancesWallet.swift in Sources */, A458FAC920ECDB480075D55E /* Cloneable.swift in Sources */, E7598F571FA1CDB20082FBE7 /* ProfileActionModel.swift in Sources */, 8557987F2093200D007050B8 /* StickerMenuActionCollectionViewCell.swift in Sources */, @@ -14907,6 +15442,7 @@ E743B58A1FB0911200F72F92 /* ParticipantsContactCell.swift in Sources */, A46679F120F10B5900DBC6B4 /* LinkModelFactory.swift in Sources */, F119E66C20D24BAF0043A532 /* MultiplePreviewViewController.swift in Sources */, + FB60B475215E611A00B59279 /* TransferHistoryHeaderView.swift in Sources */, 8528E50E2072835E00A8644A /* AudioDurationFormatter.swift in Sources */, A43B259820AB1DFA00FF8107 /* TextInputContent.swift in Sources */, E723FD221F9E59A600E0B602 /* ProfileSection.swift in Sources */, @@ -14971,6 +15507,7 @@ A43B25D820AB1EE400FF8107 /* NewChannelViewController.swift in Sources */, 850C301E204DA87A00DB26C2 /* PrivacyListInteractor.swift in Sources */, C9B8BEF7204DD7890018748C /* SettingsDataAndStorageLayout.swift in Sources */, + FB60B4C2215E770400B59279 /* SeedVerificationWalletProtocols.swift in Sources */, A49E6C4220D9A27D007D85F5 /* MainViewController+Container.swift in Sources */, 85788C4620442392003600C9 /* BuildNumberInteractor.swift in Sources */, 2605312921298BEF002E1CF1 /* Logoutputcell.swift in Sources */, @@ -14978,6 +15515,7 @@ 859B863720485F01003272B2 /* CarouselPickerViewController.swift in Sources */, 8566771E20C1579C00DD4204 /* StorageSubscriberReference.swift in Sources */, F117871420ACF018007A9A1B /* CameraSettingsWireframe.swift in Sources */, + FB60B4C5215E770400B59279 /* SeedVerificationWalletInteractor.swift in Sources */, E7E6E3DE1FB2F37900401D9E /* ParticipantsDelegate.swift in Sources */, A42D51AA206A361400EEB952 /* error.swift in Sources */, 628E2C26BE0854DB1DF64990 /* SplashWireframe.swift in Sources */, @@ -14987,6 +15525,7 @@ E7C36C351FC448CB00740630 /* DBFeature.swift in Sources */, 4B06D3122028A4CF003B275B /* GroupChatsItemsFactory.swift in Sources */, 0062D93B2062EC4100B915AC /* PhoneContact.swift in Sources */, + FB60B480215E618300B59279 /* WalletServiceResult.swift in Sources */, 85433F24204D596D00B373A7 /* WebFullScreenProtocols.swift in Sources */, 4BAB9CE42035CB0A00385520 /* ScheduleTarget.swift in Sources */, 8514F17B20EA219F00883513 /* ContextMenuConfiguration+GroupStorage.swift in Sources */, @@ -14998,10 +15537,12 @@ A45F110F20B4218D00F45004 /* ChatInitialMessage.swift in Sources */, C9B8BEFE204DEBD00018748C /* DataDownloadAndUsageMode.swift in Sources */, 2625F29F212463E8007C42B5 /* ProgressIdentifier.swift in Sources */, + FB60B467215E611A00B59279 /* CreateWalletValidationViewModel.swift in Sources */, A44B4D5220CE9BDF00CA700A /* SettingCellProtocol.swift in Sources */, 0008E9152032D6B7003E316E /* JobExtension+BERT.swift in Sources */, 85D66A0220BD963C00FBD803 /* MentionInfo.swift in Sources */, 8ED0F3D21FBC5CF2004916AB /* GroupsListWireframe.swift in Sources */, + FB60B451215E611A00B59279 /* TransferDetailsPresenter.swift in Sources */, 850C301C204DA87A00DB26C2 /* PrivacyListViewController.swift in Sources */, E791178E1F97A31D00462D68 /* ProfileViewControllerLayout.swift in Sources */, 85482846204E918000DCBEC8 /* PrivacyCellModel.swift in Sources */, @@ -15053,6 +15594,7 @@ A43B25D720AB1EE400FF8107 /* NewChannelWireFrame.swift in Sources */, A4166F5C205FE3670008F231 /* JobService.swift in Sources */, 26F03C0D20698B0000712CB0 /* ChatWheelItemModel.swift in Sources */, + FB60B4C7215E770400B59279 /* SeedBackupWalletPresenter.swift in Sources */, E4F62F7771D4BB7FE3B48FA2 /* VideoPreviewProtocols.swift in Sources */, FBD8857A2147F9640099B8C3 /* AssetsConstants.swift in Sources */, 8580BAEC20BD9A7100239D9D /* LinkRecognizable.swift in Sources */, @@ -15188,6 +15730,7 @@ FB61D662214FF96200CB2A1F /* ColorsConstants.swift in Sources */, 2648C4182069B52100863614 /* ChangeNumberStep2Protocols.swift in Sources */, 26B32B6D1FE171F700888A0A /* BadgeButton.swift in Sources */, + FB60B4CB215E770400B59279 /* SeedBackupWalletViewController.swift in Sources */, 85433F22204D596D00B373A7 /* WebFullScreenPresenter.swift in Sources */, 8580BAC620BD983400239D9D /* MessageProtocols.swift in Sources */, 2683F77A203F38E30003181A /* UIPickerView.swift in Sources */, @@ -15229,6 +15772,7 @@ 4BE2C5E82142EB5A00A73DD9 /* NynjaRingingService.swift in Sources */, 4B06D3202028A9B1003B275B /* P2pChatItemsFactory.swift in Sources */, A432CF1820B4347D00993AFB /* MaterialTextField.swift in Sources */, + FB60B44F215E611A00B59279 /* WalletDetailsViewInput.swift in Sources */, 8514F17420EA219E00883513 /* ContextMenuNextCell.swift in Sources */, A42D52D7206A53AB00EEB952 /* History_Spec.swift in Sources */, F127F3BC20BF03BF007A6F87 /* WeakDays.swift in Sources */, @@ -15236,6 +15780,7 @@ B723C629204D86AF00884FFD /* SettingsDataAndStorageWireFrame.swift in Sources */, A43B25A020AB1DFA00FF8107 /* TextFieldWithPicker.swift in Sources */, C9B8BEFC204DDDA20018748C /* CheckmarkCellLayout.swift in Sources */, + FB60B463215E611A00B59279 /* CreateWalletInteractor.swift in Sources */, 4B8BEDE1204979AA00C7D625 /* ImagesView.swift in Sources */, A45F113720B4218D00F45004 /* ReplyPreview.swift in Sources */, A481BD1C20EE72CB008FFED8 /* ReplyCounterDelegate.swift in Sources */, @@ -15248,6 +15793,7 @@ 260313A020A0A4BA009AC66D /* SwitchableActionCell.swift in Sources */, 8ED0F3D11FBC5CF2004916AB /* GroupsListViewController.swift in Sources */, BBF46945EB64E07C58817ACA /* EditGroupNameProtocols.swift in Sources */, + FB60B46B215E611A00B59279 /* TransferHistoryProtocols.swift in Sources */, 8520040020D466CE007C0036 /* StickerPack_Spec.swift in Sources */, 1E615EDDA6522EF693319BA5 /* EditGroupNameViewController.swift in Sources */, 854A4B2C2080D68200759152 /* CellWithArrowTableViewCell.swift in Sources */, @@ -15281,6 +15827,7 @@ 0062D9452062EC4100B915AC /* InviteFriendsDS.swift in Sources */, 85458CF3212D762900BA8814 /* Message+Factory.swift in Sources */, 85458CE2212D730E00BA8814 /* MessageIdentifiers.swift in Sources */, + FB60B491215E671400B59279 /* WalletBalancesOutput.swift in Sources */, F117872620ACF2DB007A9A1B /* VideoQuality.swift in Sources */, FBDA34E920921079009F4FB6 /* KeyboardLayoutGuide.swift in Sources */, 267BE2901FDEA0A700C47E18 /* SettingsGroupPresenter.swift in Sources */, @@ -15289,7 +15836,9 @@ 85D669E620BD956000FBD803 /* UIView+Shadow.swift in Sources */, 896D51F07E2F79C8B5502DBF /* EditGroupPhotoInteractor.swift in Sources */, A4ED79AA20C704F500A41F67 /* MyChannelsItemsFactory.swift in Sources */, + FB60B4C3215E770400B59279 /* SeedVerificationWalletCollectionViewCell.swift in Sources */, 5BC1D38420D3B670002A44B3 /* CallCreatorMediator.swift in Sources */, + FB60B40E215E600500B59279 /* WalletFundingNetworkResponse.swift in Sources */, B7EF8ED2210C502D00E0E981 /* InterpretationTypeTableDelegate.swift in Sources */, 1325429A6216D23E2E67B6B7 /* EditGroupPhotoWireframe.swift in Sources */, 2603139720A0A4B9009AC66D /* LangCell.swift in Sources */, @@ -15297,6 +15846,7 @@ A4213AF320D9240100B6BE7D /* PHFetchOptions+Utils.swift in Sources */, A438DB9220763AFB00AA86A2 /* Contact+Desc.swift in Sources */, 268C341921074D6C00F1472A /* TranscribeLongOperationResponseData.swift in Sources */, + FB60B445215E611A00B59279 /* WalletDetailsPresenter.swift in Sources */, 8511D3712034427F00B2A620 /* UIView+SafeArea.swift in Sources */, A42D52DF206A53AB00EEB952 /* Roster_Spec.swift in Sources */, E785F1591FF3E0C8006C52D9 /* UIFontExtension.swift in Sources */, @@ -15317,6 +15867,7 @@ A43B25AC20AB1DFA00FF8107 /* BaseInputView.swift in Sources */, F127F3BA20BF03BF007A6F87 /* DateFormatterExtension.swift in Sources */, B7B546AE210D9C8C002DCA55 /* CircleView.swift in Sources */, + FB60B479215E611A00B59279 /* TransferHistoryInteractor.swift in Sources */, E79061B81FBF2243009FD83A /* FeatureTable.swift in Sources */, 5B5EE777EF301CFC1FDCF307 /* CreateGroupInteractor.swift in Sources */, 8580BAD820BD98E700239D9D /* CounterView.swift in Sources */, @@ -15380,9 +15931,11 @@ A45F112B20B4218D00F45004 /* MessageContainerView.swift in Sources */, A7285B8B56BFCA857AD9BA8A /* AddContactByUsernameWireframe.swift in Sources */, 2603139A20A0A4B9009AC66D /* LanguageSelectorTableDelegate.swift in Sources */, + FB60B46F215E611A00B59279 /* TransferHistoryWireFrame.swift in Sources */, 853FB06A2049B193000996C5 /* SupportWireFrame.swift in Sources */, 853D0F9A20C0514E008C3684 /* UICollectionViewFlowLayout+ItemSize.swift in Sources */, A45F113920B4218D00F45004 /* AvatarViewLayout.swift in Sources */, + FB60B453215E611A00B59279 /* TransferDetailsWireFrame.swift in Sources */, B16EC832C763628A2EBBD383 /* MapSearchProtocols.swift in Sources */, A43B25A720AB1DFA00FF8107 /* ALKeyboardObservingView.swift in Sources */, A4679BAD20B2DD100021FE9C /* SubscribersSelectorProtocols.swift in Sources */, @@ -15404,10 +15957,12 @@ A4679BA720B2DD0F0021FE9C /* ChannelSubscriber.swift in Sources */, 26D238E9781B604B721C6643 /* ScheduleMessageViewController.swift in Sources */, C9405156204C8D4600D72B04 /* DataAndStorageSectionHeaderView.swift in Sources */, + FB60B47B215E611A00B59279 /* TransferHistoryTableModel.swift in Sources */, F10B0E2320B517E400528E7A /* GalleryCoordinator.swift in Sources */, 4B4266BF204D916000194BC1 /* ActionsView+Action.swift in Sources */, F77E514BE70FF0BA3130D312 /* ScheduleMessagePresenter.swift in Sources */, 8580BAEE20BD9A7100239D9D /* LinkTapGestureRecognizer.swift in Sources */, + FB60B4C1215E770400B59279 /* SeedVerificationWalletWireFrame.swift in Sources */, A42D52B1206A53AA00EEB952 /* boundaryEvent_Spec.swift in Sources */, 00D7B5C720285BA7004B0E2B /* ScheduleView.swift in Sources */, 8557989C209368E7007050B8 /* StickerPackHeaderView.swift in Sources */, @@ -15442,6 +15997,7 @@ F11786D120A98685007A9A1B /* MessageFactory.swift in Sources */, 84BB63C68EA124AA7DD21B30 /* LanguageSettingsProtocols.swift in Sources */, 69CA7311E49F87A5CACC8A73 /* LanguageSettingsViewController.swift in Sources */, + FB60B4D2215E794600B59279 /* TransferCoinstoContactsView.swift in Sources */, F13EACDB20B86B8C007104D6 /* WireframeProtocol.swift in Sources */, E27620AE3F571711EE70C0C8 /* LanguageSettingsPresenter.swift in Sources */, BC1BA70218B40F3F64841848 /* LanguageSettingsInteractor.swift in Sources */, diff --git a/Nynja/Extensions/Models/Desc/DescExtension.swift b/Nynja/Extensions/Models/Desc/DescExtension.swift index 7d7c725d5..0fc1158de 100644 --- a/Nynja/Extensions/Models/Desc/DescExtension.swift +++ b/Nynja/Extensions/Models/Desc/DescExtension.swift @@ -17,7 +17,7 @@ enum DescMime { case thumbnail(filepath: String, resolution: CGSize?) case contact(contact: Contact) case file(filename: String, filepath: String, size: Int) - case payment(mime: SendMessageType, payload: String) + case payment(mime: SendMessageType, payload: String, notes: String?) case call(members: [String]) } @@ -45,8 +45,8 @@ extension Desc { setupContact(contact) case .file(let filename, let filepath, let size): setupFile(.file, filename: filename, filepath: filepath, size: size) - case .payment(let mime, let payload): - setupBase(mime, payload: payload) + case .payment(let mime, let payload, let notes): + setupPayment(mime, payload: payload, notes: notes) case .call(let members): setupCall(members) } @@ -155,4 +155,10 @@ extension Desc { self.mime = SendMessageType.audioCall.rawValue self.data = Feature.callRepresentation(members: members) } + + private func setupPayment(_ mime: SendMessageType, payload: String, notes: String?) { + self.mime = mime.rawValue + self.payload = payload + self.data = notes.map{ Feature.payment(notes: $0) } + } } diff --git a/Nynja/Extensions/Models/Feature/FeatureExtension.swift b/Nynja/Extensions/Models/Feature/FeatureExtension.swift index 0f7588448..08082a795 100644 --- a/Nynja/Extensions/Models/Feature/FeatureExtension.swift +++ b/Nynja/Extensions/Models/Feature/FeatureExtension.swift @@ -73,6 +73,10 @@ enum FeatureKeys { enum Call: String { case users = "USERS" } + enum Payment: String { + case description = "DESCRIPTION" + case text = "text" + } } } @@ -84,6 +88,7 @@ enum FeatureGroup: String { case callData = "CALL_DATA" case pushSettings = "PUSH_SETTINGS" case none = "" + case payment = "TRANSACTION_DATA" } extension Feature { @@ -199,6 +204,18 @@ extension Feature { feature.value = _members return [feature] } + + static func payment(notes: String) -> [Feature] { + let feature = Feature() + feature.id = IdBuilder.init(format: .featureId) + .addValueForComponent(FeatureKeys.File.Payment.text.rawValue.lowercased(), .key) + .build() + feature.key = FeatureKeys.File.Payment.description.rawValue + feature.value = notes + feature.group = FeatureGroup.payment.rawValue + + return [feature] + } static func thumbRepresentation(size: Int64?, filename: String?, resolution: String) -> [Feature]? { let keys = [FeatureKeys.File.Thumb.size, diff --git a/Nynja/Generated/AssetsConstants.swift b/Nynja/Generated/AssetsConstants.swift index d0efb3e91..803ea62b1 100644 --- a/Nynja/Generated/AssetsConstants.swift +++ b/Nynja/Generated/AssetsConstants.swift @@ -483,20 +483,32 @@ internal extension Image { static var stickersIcSearch: ImageAsset { return ImageAsset(name: "stickers_ic_search") } } enum Wallet { + /// "ic_arrow_down_transfer" + static var icArrowDownTransfer: ImageAsset { return ImageAsset(name: "ic_arrow_down_transfer") } + /// "ic_arrow_down_up_transfer" + static var icArrowDownUpTransfer: ImageAsset { return ImageAsset(name: "ic_arrow_down_up_transfer") } /// "ic_arrow_transfer_down" static var icArrowTransferDown: ImageAsset { return ImageAsset(name: "ic_arrow_transfer_down") } /// "ic_arrow_transfer_up" static var icArrowTransferUp: ImageAsset { return ImageAsset(name: "ic_arrow_transfer_up") } - /// "ic_btc" - static var icBtc: ImageAsset { return ImageAsset(name: "ic_btc") } - /// "ic_eos" - static var icEos: ImageAsset { return ImageAsset(name: "ic_eos") } + /// "ic_context_blockchain" + static var icContextBlockchain: ImageAsset { return ImageAsset(name: "ic_context_blockchain") } + /// "ic_context_transfer" + static var icContextTransfer: ImageAsset { return ImageAsset(name: "ic_context_transfer") } + /// "ic_eth_disabled" + static var icEthDisabled: ImageAsset { return ImageAsset(name: "ic_eth_disabled") } /// "ic_nyn" static var icNyn: ImageAsset { return ImageAsset(name: "ic_nyn") } /// "ic_pay" static var icPay: ImageAsset { return ImageAsset(name: "ic_pay") } + /// "ic_qr_code_wallet" + static var icQrCodeWallet: ImageAsset { return ImageAsset(name: "ic_qr_code_wallet") } + /// "ic_transfer_history_empty" + static var icTransferHistoryEmpty: ImageAsset { return ImageAsset(name: "ic_transfer_history_empty") } /// "ic_wallet" static var icWallet: ImageAsset { return ImageAsset(name: "ic_wallet") } + /// "ic_wallet_absent" + static var icWalletAbsent: ImageAsset { return ImageAsset(name: "ic_wallet_absent") } } enum Wheel { enum Button { @@ -656,8 +668,6 @@ internal extension Image { static var icAddPhotoPlaceholder: ImageAsset { return ImageAsset(name: "ic_add_photo_placeholder") } /// "ic_arrow_down" static var icArrowDown: ImageAsset { return ImageAsset(name: "ic_arrow_down") } - /// "ic_arrow_right-1" - static var icArrowRight1: ImageAsset { return ImageAsset(name: "ic_arrow_right-1") } /// "ic_arrow_right" static var icArrowRight: ImageAsset { return ImageAsset(name: "ic_arrow_right") } /// "ic_arrow_up_red" diff --git a/Nynja/Generated/LocalizableConstants.swift b/Nynja/Generated/LocalizableConstants.swift index b2a681105..fd263d053 100644 --- a/Nynja/Generated/LocalizableConstants.swift +++ b/Nynja/Generated/LocalizableConstants.swift @@ -379,6 +379,8 @@ internal enum Localizable { internal static let endConferenceCallFailed = Localizable.tr("Localizable", "End_conference_call_failed") /// Enter interpretation time (1-300) internal static let enterInterpretationTime = Localizable.tr("Localizable", "enter_interpretation_time") + /// Enter passcode + internal static let enterPasscode = Localizable.tr("Localizable", "enter_passcode") /// file internal static let file = Localizable.tr("Localizable", "file") /// Files @@ -737,12 +739,12 @@ internal enum Localizable { internal static let pm = Localizable.tr("Localizable", "pm") /// Port Out to Phone internal static let portOutToPhone = Localizable.tr("Localizable", "port_out_to_phone") + /// previous + internal static let previous = Localizable.tr("Localizable", "previous") /// Price per minute internal static let pricePerMinute = Localizable.tr("Localizable", "price_per_minute") /// PRIVACY internal static let privacyScreenTitle = Localizable.tr("Localizable", "privacy screen title") - /// Balance - internal static let profileBalance = Localizable.tr("Localizable", "profile_balance") /// Added internal static let profileContactCellAdded = Localizable.tr("Localizable", "profile_contact_cell_added") /// Go to chats @@ -1019,6 +1021,8 @@ internal enum Localizable { internal static let transcribeErrorTitle = Localizable.tr("Localizable", "transcribe_error_title") /// Transcription internal static let transcribedMessage = Localizable.tr("Localizable", "transcribed_message") + /// Transfer History + internal static let transferHistoryTitle = Localizable.tr("Localizable", "transfer_history_title") /// Translate with Google internal static let translate = Localizable.tr("Localizable", "translate") /// Translation @@ -1097,18 +1101,82 @@ internal enum Localizable { internal static let walletAlert = Localizable.tr("Localizable", "wallet_alert") /// Amount internal static let walletAmount = Localizable.tr("Localizable", "wallet_amount") - /// Currency Balance + /// Balance internal static let walletBalance = Localizable.tr("Localizable", "wallet_balance") + /// Coming soon + internal static let walletComingSoon = Localizable.tr("Localizable", "wallet_coming_soon") + /// Confirm pin code + internal static let walletConfirmPin = Localizable.tr("Localizable", "wallet_confirm_pin") + /// create a wallet + internal static let walletCreate = Localizable.tr("Localizable", "wallet_create") /// Wallet has been created internal static let walletCreated = Localizable.tr("Localizable", "wallet_created") + /// wallet details + internal static let walletDetails = Localizable.tr("Localizable", "wallet_details") + /// You don't have any wallet. Please create your first wallet now. + internal static let walletDontHaveAny = Localizable.tr("Localizable", "wallet_dont_have_any") + /// You have no transaction history yet. You can send coins to other users with a chat or directly to any address from within the wallet. + internal static let walletEmptyHistory = Localizable.tr("Localizable", "wallet_empty_history") + /// Enter pin code (6 digits) + internal static let walletEnterPin = Localizable.tr("Localizable", "wallet_enter_pin") + /// Enter word + internal static let walletEnterWord = Localizable.tr("Localizable", "wallet_enter_word") + /// External address + internal static let walletExternalAddress = Localizable.tr("Localizable", "wallet_external_address") /// Wallet internal static let walletHeader = Localizable.tr("Localizable", "wallet_header") - /// Recepient - internal static let walletRecepient = Localizable.tr("Localizable", "wallet_recepient") - /// send coins + /// Wallet name + internal static let walletName = Localizable.tr("Localizable", "wallet_name") + /// The length of the name must be between %@ and %@ + internal static func walletNameLength(_ p1: String, _ p2: String) -> String { + return Localizable.tr("Localizable", "wallet_name_length", p1, p2) + } + /// Notes to the recipient + internal static let walletNotes = Localizable.tr("Localizable", "wallet_notes") + /// Notes to the recipient + internal static let walletNotesToRecipient = Localizable.tr("Localizable", "wallet_notes_to_recipient") + /// Your NYNJAcoin Address: + internal static let walletNynjaAdress = Localizable.tr("Localizable", "wallet_nynja_adress") + /// Pin code length must be between equal to %@ + internal static func walletPasscodeLength(_ p1: String) -> String { + return Localizable.tr("Localizable", "wallet_passcode_length", p1) + } + /// Pin code doesn't math + internal static let walletPasscodeMatch = Localizable.tr("Localizable", "wallet_passcode_match") + /// This pin code will be required when sending coins from your wallets. + internal static let walletPinRequire = Localizable.tr("Localizable", "wallet_pin_require") + /// Recipient + internal static let walletRecipient = Localizable.tr("Localizable", "wallet_recipient") + /// seed backup + internal static let walletSeedBackup = Localizable.tr("Localizable", "wallet_seed_backup") + /// The following 12 words are used to recover your wallet. Write them down correctly on piece of paper and keep them safe.\nWe do not store your words and if you cannot remember them, you will not be able to access to your wallet in the future.\nAfter we show you the 12 words, we will request you to re-enter them to confirm you wrote them down.\nRemember: Whoever has access to your 12 words, has access to your wallet. + internal static let walletSeedDescription = Localizable.tr("Localizable", "wallet_seed_description") + /// seed verification + internal static let walletSeedVerification = Localizable.tr("Localizable", "wallet_seed_verification") + /// Please enter the words you just wrote down, one after another, to make sure that everything is backed up correctly. + internal static let walletSeedVerificationDescription = Localizable.tr("Localizable", "wallet_seed_verification_description") + /// send internal static let walletSend = Localizable.tr("Localizable", "wallet_send") + /// Show in blockchain + internal static let walletShowInBlockchain = Localizable.tr("Localizable", "wallet_show_in_blockchain") + /// start verification + internal static let walletStartVerification = Localizable.tr("Localizable", "wallet_start_verification") + /// Confirmations + internal static let walletTransferConfirmations = Localizable.tr("Localizable", "wallet_transfer_confirmations") /// The message with Transfer Information will be deleted only for you in chat history, but this message will be still available in Transfer History internal static let walletTransferDeletionInfo = Localizable.tr("Localizable", "wallet_transfer_deletion_info") + /// transfer details + internal static let walletTransferDetails = Localizable.tr("Localizable", "wallet_transfer_details") + /// Transfers + internal static let walletTransfers = Localizable.tr("Localizable", "wallet_transfers") + /// Success + internal static let walletVerificationSuccess = Localizable.tr("Localizable", "wallet_verification_success") + /// You have successfully created your wallet. Don’t forget to store your 12 words safely. + internal static let walletVerificationSuccessDescription = Localizable.tr("Localizable", "wallet_verification_success_description") + /// View on blockchain explorer: + internal static let walletViewOnBlockchain = Localizable.tr("Localizable", "wallet_view_on_blockchain") + /// Word + internal static let walletWord = Localizable.tr("Localizable", "wallet_word") /// We internal static let we = Localizable.tr("Localizable", "we") /// current position diff --git a/Nynja/KeychainService/KeychainService.swift b/Nynja/KeychainService/KeychainService.swift index 50c48a0ae..2928f1e7b 100644 --- a/Nynja/KeychainService/KeychainService.swift +++ b/Nynja/KeychainService/KeychainService.swift @@ -18,8 +18,14 @@ private extension KeychainService { } } +extension KeychainService { + enum Keys { + static let dataBasePassphrase = "com.nynja.mobile.communicator.storage.service.passphrase" + } +} + final class KeychainService { - + private let queryFactory = QueryFactory() // MARK: Init diff --git a/Nynja/Library/MessageFactory/MessageFactory.swift b/Nynja/Library/MessageFactory/MessageFactory.swift index 0326fd7c6..6ca6b294d 100644 --- a/Nynja/Library/MessageFactory/MessageFactory.swift +++ b/Nynja/Library/MessageFactory/MessageFactory.swift @@ -31,9 +31,9 @@ final class MessageFactory: MessageFactoryProtocol, InitializeInjectable { // MARK: - MessageFactoryProtocol - - func makePaymentMessage(inputText: String, contact: Contact) -> Message { - let descMimes = [DescMime.payment(mime: .payment, payload: inputText)] + + func makePaymentMessage(inputText: String, contact: Contact, notes: String?) -> Message { + let descMimes = [DescMime.payment(mime: .transfer, payload: inputText, notes: notes)] let descs = descMimes.map { Desc(mime: $0) } diff --git a/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift b/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift index 5fde105e4..3e19e0772 100644 --- a/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift +++ b/Nynja/Library/MessageFactory/MessageFactoryProtocol.swift @@ -36,7 +36,7 @@ protocol MessageFactoryProtocol: class { func makeTranscribedMessage(message: Message, text: String, lang: String) -> Message func makeUntranscribedMessage(message: Message, transcriptionId: String, translationId: String?) -> Message - func makePaymentMessage(inputText: String, contact: Contact) -> Message + func makePaymentMessage(inputText: String, contact: Contact, notes: String?) -> Message func makeCallMessage(members: [String], room: Room?) -> Message diff --git a/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift b/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift index f96280473..3c960b9aa 100644 --- a/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift +++ b/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift @@ -29,6 +29,8 @@ extension NynjaContextMenuItemsFactory { rows = NynjaContextMenuItemsFactory.stickerMessageItems(for: model) case .audioCall: rows = NynjaContextMenuItemsFactory.audioCallMessageItems(for: model) + case .transfer: + rows = NynjaContextMenuItemsFactory.makeTransferMessageItems(for: model) default: rows = [ ContextMenuRow(items: [ @@ -84,7 +86,7 @@ extension NynjaContextMenuItemsFactory { starItem(for: model), reply(), forward(), delete()] ), ContextMenuRow(items: [ - model.isTranslated ? untranslate(with: .double) : translate(with: .double), + translation(for: model), copy(), shouldAddEdit(for: model) ? edit() : placeholder()] ), @@ -137,7 +139,7 @@ extension NynjaContextMenuItemsFactory { let shareItem = share(isEnabled: true) observeDownloadingState(for: model, userInfo: userInfo) { [weak saveItem, weak shareItem] url in - guard let saveItem = saveItem, let shareItem = shareItem else { + guard let saveItem = saveItem, let _ = shareItem else { return } // TODO: [saveItem, shareItem] @@ -191,7 +193,7 @@ extension NynjaContextMenuItemsFactory { starItem(for: model), copyConvertion(with: .default), doublePlaceholder()] ), ContextMenuRow(items: [ - model.isTranslated ? untranslate(with: .double) : translate(with: .double), + translation(for: model), chooseTranslationLanguage(with: .double)] ) ] @@ -202,7 +204,7 @@ extension NynjaContextMenuItemsFactory { ContextMenuRow(items: [ starItem(for: model), copyConvertion(with: .default), - model.isTranslated ? untranslate(with: .double) : translate(with: .double)] + translation(for: model)] ), ContextMenuRow(items: [ doublePlaceholder(), @@ -216,7 +218,7 @@ extension NynjaContextMenuItemsFactory { ContextMenuRow(items: [ starItem(for: model), copyConvertion(with: .default), - model.isTranslated ? untranslate(with: .double) : translate(with: .double)] + translation(for: model)] ), ContextMenuRow(items: [ model.isTranscribed ? untranscribe(with: .double) : transcribe(with: .double), @@ -224,7 +226,29 @@ extension NynjaContextMenuItemsFactory { ) ] } - + + private static func makeTransferMessageItems(for model: BaseChatCellModel) -> [ContextMenuRow] { + return [ + ContextMenuRow(items: [ + starItem(for: model), + reply(), + delete(), + transfers()] + ), + ContextMenuRow(items: [ + showInBlockChain(with: .double), + translation(for: model), + ] + ) + ] + } + + private static func translation( + for model: BaseChatCellModel, + with layout: ContextMenuItem.LayoutRepresentation = .double) -> ContextMenuItem { + return model.isTranslated ? untranslate(with: layout) : translate(with: layout) + } + private static func observeDownloadingState(for model: BaseChatCellModel, userInfo: NynjaContextMenuUserInfo?, handler: @escaping (URL?) -> Void) { @@ -344,27 +368,49 @@ extension NynjaContextMenuItemsFactory { static func doublePlaceholder() -> ContextMenuItem { return ContextMenuItem(content: .placeholder, layout: .double) } + + static func transfers(with layout: ContextMenuItem.LayoutRepresentation = .default) -> ContextMenuItem { + return ContextMenuItem( + content: .item( + title: Strings.transfers.localized, + icon: UIImage.nynja.Wallet.icContextTransfer.image, + action: .transfers), + layout: layout + ) + } + + static func showInBlockChain(with layout: ContextMenuItem.LayoutRepresentation = .default) -> ContextMenuItem { + return ContextMenuItem( + content: .item( + title: Strings.showInBlockchain.localized, + icon: UIImage.nynja.Wallet.icContextBlockchain.image, + action: .showInBlockchain), + layout: layout + ) + } } // MARK: - Strings private enum Strings: String { - case star = "star" - case unstar = "unstar" - case reply = "reply" - case edit = "edit" - case forward = "forward" - case translate = "translate" - case untranslate = "untranslate" - case chooseLanguage = "choose_language" - case transcribe = "transcribe" - case untranscribe = "untranscribe" - case copy = "copy" - case delete = "delete" - case share = "share" - case saveToGallery = "save_to_gallery" - case saveToDownloads = "save_to_downloads" - case marketplace = "marketplace" + case star = "star" + case unstar = "unstar" + case reply = "reply" + case edit = "edit" + case forward = "forward" + case translate = "translate" + case untranslate = "untranslate" + case chooseLanguage = "choose_language" + case transcribe = "transcribe" + case untranscribe = "untranscribe" + case copy = "copy" + case delete = "delete" + case share = "share" + case saveToGallery = "save_to_gallery" + case saveToDownloads = "save_to_downloads" + case marketplace = "marketplace" + case transfers = "wallet_transfers" + case showInBlockchain = "wallet_show_in_blockchain" var localized: String { return rawValue.localized diff --git a/Nynja/Library/UI/Extensions/StringExtensions.swift b/Nynja/Library/UI/Extensions/StringExtensions.swift index c1957a6d0..ee4173bed 100644 --- a/Nynja/Library/UI/Extensions/StringExtensions.swift +++ b/Nynja/Library/UI/Extensions/StringExtensions.swift @@ -347,3 +347,16 @@ extension String { } } + +extension String { + static func replace(in text: String?, range: NSRange, string: String) -> String { + guard let text = text else { + return string + } + + let lowerBound = text.index(text.startIndex, offsetBy: range.lowerBound) + let upperBound = text.index(text.startIndex, offsetBy: range.upperBound) + return text.replacingCharacters(in: lowerBound.. Bool { return shouldTextChanged?(self, range, text) ?? true } - + } diff --git a/Nynja/Modules/Contacts/ContactsProtocols.swift b/Nynja/Modules/Contacts/ContactsProtocols.swift index 67e506d19..6e03789be 100644 --- a/Nynja/Modules/Contacts/ContactsProtocols.swift +++ b/Nynja/Modules/Contacts/ContactsProtocols.swift @@ -10,39 +10,35 @@ import UIKit protocol ContactsWireFrameProtocol: class { - func presentContacts(navigation: UINavigationController, contactsViewMode: ContactViewMode, mainWireFrame: MainWireFrame) - - /** - * Add here your methods for communication PRESENTER -> WIREFRAME - */ + func presentContactsForSharing(navigation: UINavigationController, mainWireFrame: MainWireFrame?) + func presentAllContacts(navigation: UINavigationController, mainWireFrame: MainWireFrame?) + func presentMyContacts(navigation: UINavigationController, mainWireFrame: MainWireFrame?) + func presentContactsForCoinsSending(navigation: UINavigationController) + func shareContact(contact: Contact) func showInviteFriends() func showAddContactScreen(contact: Contact) func showContact(contact: Contact) + func back() + func showComingSoonAlert() } protocol ContactsViewProtocol: class { var presenter: ContactsPresenterProtocol! { get set } - - /** - * Add here your methods for communication PRESENTER -> VIEW - */ - + func setupScreenTitle(_ title: String?) func setupFriendsWith(groupedContacts: GroupedContacts, contactsViewMode: ContactViewMode) - func configureUI(forMyContacts isMyContacts: Bool) + func configureUI(for contactsViewMode: ContactViewMode) + func configureNavigationView(for contactsViewMode: ContactViewMode) } -protocol ContactsPresenterProtocol: BasePresenterProtocol { +protocol ContactsPresenterProtocol: BasePresenterProtocol, NavigationProtocol { var view: ContactsViewProtocol! { get set } var interactor: ContactsInteractorInputProtocol! { get set } var wireFrame: ContactsWireFrameProtocol! { get set } - - /** - * Add here your methods for communication VIEW -> PRESENTER - */ + func viewLoaded() func handleTap(on contact: Contact) func filter(with text: String) @@ -50,13 +46,11 @@ protocol ContactsPresenterProtocol: BasePresenterProtocol { func showInviteFriends() func showAddContactScreen(contact: Contact) func add(contact: Contact) + func walletAdressTextFieldClicked() } protocol ContactsInteractorOutputProtocol: class { - - /** - * Add here your methods for communication INTERACTOR -> PRESENTER - */ + func fetched(contacts: [Contact]) func filtered(contacts: [Contact]) } @@ -64,11 +58,7 @@ protocol ContactsInteractorOutputProtocol: class { protocol ContactsInteractorInputProtocol: BaseInteractorProtocol { var presenter: ContactsInteractorOutputProtocol! { get set } - - /** - * Add here your methods for communication PRESENTER -> INTERACTOR - */ - + func filter(with text: String) func add(contact: Contact) } diff --git a/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift b/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift index 5d089698f..6dfd48079 100644 --- a/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift +++ b/Nynja/Modules/Contacts/Interactor/ContactsInteractor.swift @@ -34,7 +34,7 @@ class ContactsInteractor: BaseInteractor, ContactsInteractorInputProtocol, IoHan switch contactViewMode { case .allContacts: searchContactsInSystem() - case .myContacts: + case .myContacts, .transferContacts: fetchFriends() case .shareContact: fetchAll() @@ -47,7 +47,7 @@ class ContactsInteractor: BaseInteractor, ContactsInteractorInputProtocol, IoHan func filter(with text: String) { var filteredContacts: [Contact] switch contactViewMode { - case .allContacts: + case .allContacts, .transferContacts: filteredContacts = contacts case .myContacts, .shareContact: filteredContacts = contacts.filter { @@ -106,7 +106,7 @@ class ContactsInteractor: BaseInteractor, ContactsInteractorInputProtocol, IoHan switch contactViewMode { case .allContacts: presenter.fetched(contacts: contacts) - case .myContacts, .shareContact: + case .myContacts, .shareContact, .transferContacts: let filteredContacts = contacts.filter { [.friend, .banned, .ban].contains($0.originalStatus) } @@ -118,7 +118,7 @@ class ContactsInteractor: BaseInteractor, ContactsInteractorInputProtocol, IoHan guard let phoneId = contact.phone_id, phoneId != storageService.phoneId else { return } switch contactViewMode { - case .allContacts: + case .allContacts, .transferContacts: guard let index = contacts.index(where: { $0.phoneId == phoneId }) else { return } @@ -141,7 +141,7 @@ class ContactsInteractor: BaseInteractor, ContactsInteractorInputProtocol, IoHan switch contactViewMode { case .allContacts: self.contacts = contacts.withoutSelf - case .myContacts, .shareContact: + case .myContacts, .shareContact, .transferContacts: self.contacts = contacts } diff --git a/Nynja/Modules/Contacts/Presenter/ContactsPresenter.swift b/Nynja/Modules/Contacts/Presenter/ContactsPresenter.swift index 7e89af398..fcd8b2310 100644 --- a/Nynja/Modules/Contacts/Presenter/ContactsPresenter.swift +++ b/Nynja/Modules/Contacts/Presenter/ContactsPresenter.swift @@ -49,16 +49,15 @@ class ContactsPresenter: BasePresenter, ContactsPresenterProtocol, ContactsInter switch contactsViewMode { case .myContacts: title = Constants.LocalizableKeys.contacts.localized - view.configureUI(forMyContacts: true) case .allContacts: title = Constants.LocalizableKeys.byContacts.localized - view.configureUI(forMyContacts: false) default: title = Constants.LocalizableKeys.contacts.localized - view.configureUI(forMyContacts: false) } - + + view.configureUI(for: contactsViewMode) view.setupScreenTitle(title) + view.configureNavigationView(for: contactsViewMode) } func filter(with text: String) { @@ -98,6 +97,12 @@ class ContactsPresenter: BasePresenter, ContactsPresenterProtocol, ContactsInter func add(contact: Contact) { self.interactor.add(contact: contact) } + + func walletAdressTextFieldClicked() { + dispatchAsyncMain { [weak self] in + self?.wireFrame.showComingSoonAlert() + } + } // MARK: - ContactsInteractorOutputProtocol @@ -110,3 +115,9 @@ class ContactsPresenter: BasePresenter, ContactsPresenterProtocol, ContactsInter self.view.setupFriendsWith(groupedContacts: contacts.groupedContacts, contactsViewMode: contactsViewMode) } } + +extension ContactsPresenter { + func back() { + wireFrame.back() + } +} diff --git a/Nynja/Modules/Contacts/View/TransferCoinstoContactsView.swift b/Nynja/Modules/Contacts/View/TransferCoinstoContactsView.swift new file mode 100644 index 000000000..e13f16609 --- /dev/null +++ b/Nynja/Modules/Contacts/View/TransferCoinstoContactsView.swift @@ -0,0 +1,52 @@ +// +// TransferCoinstoContactsView.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/6/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +final class TransferCoinstoContactsView: UIControl { + + var touchUpInsideHendler: (() ->())? + + override init(frame: CGRect) { + super.init(frame: frame) + baseSetup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + baseSetup() + } + + private lazy var adressTextField: MaterialTextField = { + + let textField = MaterialTextField() + textField.backgroundColor = UIColor.nynja.clear + textField.isUserInteractionEnabled = false + textField.rightView = UIImageView(image: UIImage.nynja.Wallet.icQrCodeWallet.image) + textField.rightViewMode = UITextFieldViewMode.always + textField.cursorColor = UIColor.nynja.white + + addSubview(textField) + + textField.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + return textField + }() +} + +private extension TransferCoinstoContactsView { + func baseSetup() { + adressTextField.placeholder = "wallet_external_address".localized + + addTarget(self, action: #selector(touchedUpInside), for: UIControlEvents.touchUpInside) + } + + @objc func touchedUpInside() { + touchUpInsideHendler?() + } +} diff --git a/Nynja/Modules/Contacts/View/ViewController/ContactsViewController.swift b/Nynja/Modules/Contacts/View/ViewController/ContactsViewController.swift index ee8a624d0..c6d5d2a0c 100644 --- a/Nynja/Modules/Contacts/View/ViewController/ContactsViewController.swift +++ b/Nynja/Modules/Contacts/View/ViewController/ContactsViewController.swift @@ -42,6 +42,19 @@ class ContactsViewController: BaseVC, ContactsViewProtocol, ContactCellDelegate, }) return inviteFriendHeader }() + + lazy var transferCoinsView: TransferCoinstoContactsView = { + let transferCoinsView = TransferCoinstoContactsView() + + view.addSubview(transferCoinsView) + transferCoinsView.snp.makeConstraints({ (make) in + make.left.right.equalToSuperview().inset(Constraints.transferCoinsView.inset) + make.height.equalTo(Constraints.transferCoinsView.height) + make.top.equalTo(navigationView.snp.bottom) + }) + + return transferCoinsView + }() private lazy var tableView: UITableView = { let tableView = UITableView.default @@ -105,6 +118,10 @@ class ContactsViewController: BaseVC, ContactsViewProtocol, ContactCellDelegate, self.tableView.isHidden = false self.controlContainerView.isHidden = false presenter.viewLoaded() + + transferCoinsView.touchUpInsideHendler = { [weak self] in + self?.presenter.walletAdressTextFieldClicked() + } } @@ -130,6 +147,16 @@ class ContactsViewController: BaseVC, ContactsViewProtocol, ContactCellDelegate, func setupScreenTitle(_ title: String?) { screenTitle = title?.uppercased() } + + func configureNavigationView(for contactsViewMode: ContactViewMode) { + guard case .transferContacts = contactsViewMode else { return } + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: false, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + } func setupFriendsWith(groupedContacts: GroupedContacts, contactsViewMode: ContactViewMode) { tableDataSource.groupedContacts = groupedContacts @@ -137,18 +164,35 @@ class ContactsViewController: BaseVC, ContactsViewProtocol, ContactCellDelegate, self.tableView.reloadData() } - func configureUI(forMyContacts isMyContacts: Bool) { + func configureUI(for contactsViewMode: ContactViewMode) { self.tableView.snp.remakeConstraints { [weak self] (make) in make.left.right.equalToSuperview() guard let this = self else { return } - if isMyContacts { + + switch contactsViewMode { + case .myContacts: make.top.equalTo(this.inviteFriendsView.snp.bottom) - } else { + case .transferContacts: + make.top.equalTo(this.transferCoinsView.snp.bottom) + case .allContacts, .shareContact: make.top.equalTo(this.navigationView.snp.bottom) } } - - self.shouldShowSeparator = !isMyContacts + + switch contactsViewMode { + case .myContacts: + shouldShowSeparator = true + transferCoinsView.isHidden = true + inviteFriendsView.isHidden = false + case .transferContacts: + transferCoinsView.isHidden = false + shouldShowSeparator = true + inviteFriendsView.isHidden = true + case .allContacts, .shareContact: + shouldShowSeparator = false + transferCoinsView.isHidden = true + inviteFriendsView.isHidden = true + } } @@ -220,5 +264,10 @@ extension ContactsViewController { struct controlContainerView { static let bottomInset: CGFloat = 28.0 } + + struct transferCoinsView { + static let inset = CGFloat(16.0.adjustedByWidth) + static let height = CGFloat(70.0.adjustedByWidth) + } } } diff --git a/Nynja/Modules/Contacts/WireFrame/ContactsWireframe.swift b/Nynja/Modules/Contacts/WireFrame/ContactsWireframe.swift index a7f24db29..9cd455cfd 100644 --- a/Nynja/Modules/Contacts/WireFrame/ContactsWireframe.swift +++ b/Nynja/Modules/Contacts/WireFrame/ContactsWireframe.swift @@ -13,41 +13,72 @@ enum ContactViewMode { case myContacts case allContacts case shareContact + case transferContacts } class ContactsWireFrame: ContactsWireFrameProtocol { weak var navigation : UINavigationController? weak var mainWF: MainWireFrame? - - func presentContacts(navigation: UINavigationController, - contactsViewMode: ContactViewMode = .allContacts, - mainWireFrame: MainWireFrame) { + + @discardableResult + private func presentContacts(navigation: UINavigationController, + contactsViewMode: ContactViewMode, + mainWireFrame: MainWireFrame? = nil) -> UIViewController { + self.navigation = navigation + self.mainWF = mainWireFrame + let view = makeContactsView(for: contactsViewMode) + navigation.pushViewController(view, animated: false) + + return view + } + + private func makeContactsView(for contactsViewMode: ContactViewMode) -> UIViewController { let view = ContactsViewController() + let presenter = ContactsPresenter(mode: contactsViewMode) - let interactorDependencies = ContactsInteractor.Dependencies(presenter: presenter, mqttService: MQTTService.sharedInstance, storageService: StorageService.sharedInstance) + let interactorDependencies = ContactsInteractor.Dependencies( + presenter: presenter, + mqttService: MQTTService.sharedInstance, + storageService: StorageService.sharedInstance) let interactor = ContactsInteractor(mode: contactsViewMode) - - self.navigation = navigation - self.mainWF = mainWireFrame - + interactor.inject(dependencies: interactorDependencies) + // Connecting view.presenter = presenter presenter.view = view presenter.wireFrame = self presenter.interactor = interactor - - interactor.inject(dependencies: interactorDependencies) - - navigation.pushViewController(view as UIViewController, animated: false) - if contactsViewMode != .shareContact { - self.navigation?.viewControllers = [view] - } + + return view as UIViewController + } +} + +extension ContactsWireFrame { + + func presentContactsForSharing(navigation: UINavigationController, mainWireFrame: MainWireFrame?) { + let view = presentContacts( + navigation: navigation, + contactsViewMode: .shareContact, + mainWireFrame: mainWireFrame) + navigation.viewControllers = [view] + } + + func presentAllContacts(navigation: UINavigationController, mainWireFrame: MainWireFrame?) { + presentContacts(navigation: navigation, contactsViewMode: .allContacts, mainWireFrame: mainWireFrame) + } + + func presentMyContacts(navigation: UINavigationController, mainWireFrame: MainWireFrame?) { + presentContacts(navigation: navigation, contactsViewMode: .myContacts, mainWireFrame: mainWireFrame) } - + + func presentContactsForCoinsSending(navigation: UINavigationController) { + presentContacts(navigation: navigation, contactsViewMode: .transferContacts) + } + func shareContact(contact: Contact) { navigation?.popViewController(animated: true) - self.mainWF?.shareContact(contact: contact) + mainWF?.shareContact(contact: contact) } func showInviteFriends() { @@ -60,8 +91,17 @@ class ContactsWireFrame: ContactsWireFrameProtocol { } OtherUserWireFrame().present(navigation: navigation, main: mainWF, contact: contact) } - + func showContact(contact: Contact) { mainWF?.showAddContact(contact: contact) } + + func back() { + guard let navigation = navigation else { return } + navigation.popViewController(animated: true) + } + + func showComingSoonAlert() { + AlertManager.sharedInstance.showImageAlert(with: nil, message: "wallet_coming_soon".localized) + } } diff --git a/Nynja/Modules/Main/MainProtocols.swift b/Nynja/Modules/Main/MainProtocols.swift index ba545e5f6..3949b1eb5 100644 --- a/Nynja/Modules/Main/MainProtocols.swift +++ b/Nynja/Modules/Main/MainProtocols.swift @@ -47,7 +47,7 @@ protocol MainWireFrameProtocol: class { func showAddParticipantsToCreateCallWith(room:Room) func showAddParticipantsToCreateConferenceCall() - func showPaymentFor(_ contact: Contact) + func showPayment(profile: Profile, to contact: Contact) func showScheduleMessage(with mode: ScheduledMessageMode, delegate: ScheduleMessageDelegate?) func showScheduleMessage(with inputMessage: InputScheduleMessage, delegate: ScheduleMessageDelegate?) diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index 1a00ec086..50401f99a 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -209,15 +209,18 @@ class MainPresenter: MainPresenterProtocol, MainInteractorOutputProtocol, Schedu func showPayment() { var contactOptional: Contact? { let key = String(SharedParameters.contactPhoneId.rawValue) - guard let contactID = UserDefaults.standard.value(forKey: key) as? String, - let dbContact = ContactDAO.fetchContact(by: contactID) else { return nil } - return Contact(contact: dbContact) + guard + let contactID = UserDefaults.standard.value(forKey: key) as? String, + let contact = ContactDAO.findContactBy(phoneId: contactID) else { + return nil + } + return contact } - if let contact = contactOptional { - wireFrame.showPaymentFor(contact) + if let contact = contactOptional, let profile = ProfileDAO.currentProfile { + wireFrame.showPayment(profile: profile, to: contact) } else { - assertionFailure("Contact not found") + assertionFailure("Contact or profile not found") } } diff --git a/Nynja/Modules/Main/WireFrame/MainWireframe.swift b/Nynja/Modules/Main/WireFrame/MainWireframe.swift index 7774e1b29..a71af034b 100644 --- a/Nynja/Modules/Main/WireFrame/MainWireframe.swift +++ b/Nynja/Modules/Main/WireFrame/MainWireframe.swift @@ -66,7 +66,7 @@ class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { } func showMyContacts() { - ContactsWireFrame().presentContacts(navigation: contentNavigation, contactsViewMode: .myContacts, mainWireFrame: self) + ContactsWireFrame().presentMyContacts(navigation: contentNavigation, mainWireFrame: self) } func showContacts() { @@ -74,7 +74,7 @@ class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { guard let `self` = self, status == .authorized else { return } - ContactsWireFrame().presentContacts(navigation: self.contentNavigation, mainWireFrame: self) + ContactsWireFrame().presentAllContacts(navigation: self.contentNavigation, mainWireFrame: self) } } @@ -95,13 +95,14 @@ class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { } func showContactsToShare() { - ContactsWireFrame().presentContacts(navigation: contentNavigation, contactsViewMode: .shareContact, - mainWireFrame: self) + ContactsWireFrame().presentContactsForSharing(navigation: contentNavigation, mainWireFrame: self) } - func showPaymentFor(_ contact: Contact) { - guard let nav = navigation else { return } - PaymentWireFrame().presentPayment(navigation: nav, contact: contact) + func showPayment(profile: Profile, to contact: Contact) { + guard let navigation = navigation else { + return + } + PaymentWireFrame().presentPayment(navigation: navigation, contact: contact, profile: profile) } func showInviteFriends() { diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift index 341dd671d..ab6c474cd 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+Translation.swift @@ -257,10 +257,23 @@ extension MessageInteractor { extension MessageInteractor { //MARK: - Processing - + + private func textForTranslation(from message: Message) -> String? { + guard let attach = message.mainFile, let mime = attach.mime, let type = SendMessageType(rawValue: mime) else { + return nil + } + + switch type { + case .transfer: + return attach.data?.first?.value + default: + return (messageParser.parse(message, to: .transcription).first?.payload ?? attach.payload) + } + } + func translate(_ message: Message, lang: SelectedLang? = nil, completion: TranslationComletion? = nil) { - guard let id = message.msg_id, let text = (messageParser.parse(message, to: .transcription).first?.payload ?? message.mainFile?.payload) else { - return + guard let id = message.msg_id, let text = textForTranslation(from: message) else { + return } var traslationLanguage = conversationLanguageSettingService.chatLanguage.lang diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor.swift b/Nynja/Modules/Message/Interactor/MessageInteractor.swift index fb4152c99..639fe6186 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor.swift @@ -436,13 +436,6 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } // MARK: - Send Message - - func sendPayment(_ payload: String) { - guard let contact = self.contact else { return } - let message = messageFactory.makePaymentMessage(inputText: payload, contact: contact) - sendMessage(message) - } - func sendMessage(_ message: InputTextMessage) { let message = messageFactory.makeTextMessage(inputText: message, contact: contact, room: room) sendMessage(message) diff --git a/Nynja/Modules/Message/Presenter/MessagePresenter.swift b/Nynja/Modules/Message/Presenter/MessagePresenter.swift index 6dc7f88e7..771542b31 100644 --- a/Nynja/Modules/Message/Presenter/MessagePresenter.swift +++ b/Nynja/Modules/Message/Presenter/MessagePresenter.swift @@ -55,7 +55,21 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract return ChannelChatItemsFactory(isActionsEnabled: isActionsEnabled) } } else { - return P2pChatItemsFactory(isActionsEnabled: isActionsEnabled, isPaymentEnabled: false) { [weak self] in + var paymentsEnabled: Bool { + guard let contact = interactor.contact else { + assertionFailure("contact should exist") + return false + } + guard let profile = ProfileDAO.currentProfile else { + assertionFailure("user profile should exist") + return false + } + let opponentWallet = ContactServices(contact: contact).wallet != nil + let myWallet = ProfileServices(profile: profile).wallet != nil + + return opponentWallet && myWallet + } + return P2pChatItemsFactory(isActionsEnabled: isActionsEnabled, isPaymentEnabled: paymentsEnabled) { [weak self] in let isBan = self?.interactor.contact?.isBan ?? false if isBan { @@ -245,6 +259,14 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract } // MARK: - Context menu + + func openTransfersHistoty() { + guard let profile = ProfileDAO.currentProfile else { + return + } + wireFrame.openTransfersHistoty(for: profile) + } + func copyMessage(localId: String) { interactor.copyMessage(localId: localId) } @@ -580,14 +602,15 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract cells.append(systemModel(message: systemMessage, mod: message)) } else if let localId = message.msg_id, - var model = self.getCellModel(message: message, - repliedModel: repliedModel, - readerID: configuration.reader, - links: configuration.links[localId], - mentions: configuration.mentions[localId], - translation: configuration.translations[localId], - transcription: configuration.transcriptions[localId], - starId: configuration.stars[localId]?.client_id) { + var model = self.getCellModel( + message: message, + repliedModel: repliedModel, + readerID: configuration.reader, + links: configuration.links[localId], + mentions: configuration.mentions[localId], + translation: configuration.translations[localId], + transcription: configuration.transcriptions[localId], + starId: configuration.stars[localId]?.client_id) { if let attach = message.mainFile?.payload, let url = URL(string: attach), @@ -788,6 +811,11 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract model.audioDuration = desc.audioDuration case .sticker: model.emoji = message.mainFile?.firstEmoji + case .transfer: + guard let files = message.files?.first, files.mime == SendMessageType.transfer.rawValue else { + break + } + model.transferNotes = files.data?.first?.value default: break } @@ -859,13 +887,14 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract func newMessage(with configuration: MessageConfiguration) { if let systemMessage = configuration.message.systemMessage(for: interactor.chat) { self.view.newMessage(systemModel(message: systemMessage, mod: configuration.message)) - } else if var model = self.getCellModel(message: configuration.message, - repliedModel: configuration.repliedModel, - readerID: configuration.readerID, - links: configuration.links, - mentions: configuration.mentions, - translation: configuration.translation, - transcription: configuration.transcription) { + } else if var model = self.getCellModel( + message: configuration.message, + repliedModel: configuration.repliedModel, + readerID: configuration.readerID, + links: configuration.links, + mentions: configuration.mentions, + translation: configuration.translation, + transcription: configuration.transcription) { model = self.attachProgress(model: model, progressModel: configuration.progressModel) self.view.newMessage(model) @@ -1036,14 +1065,14 @@ private extension MessagePresenter { if isMyselfChat { self = .myselfDelete + } else if messageType == .transfer { + self = .transeferDeleteConfirmation } else if !isOwner { self = .notOwnerDeleteForMe } else if !canDeleteForOthers { self = .ownerDeleteForMe } else if !isP2PChat { self = .ownerDeleteForAll - } else if messageType == .payment { - self = .transeferDeleteConfirmation } else { self = .ownerDeleteForBoth } diff --git a/Nynja/Modules/Message/Protocols/MessageProtocols.swift b/Nynja/Modules/Message/Protocols/MessageProtocols.swift index e863ea381..18789a8a2 100644 --- a/Nynja/Modules/Message/Protocols/MessageProtocols.swift +++ b/Nynja/Modules/Message/Protocols/MessageProtocols.swift @@ -39,8 +39,10 @@ protocol MessageWireframeProtocol: DocumentInteractionInput { func openURL(url: URL) func openChat(_ chat: ChatModel, originLocalId: String?) func showLanguageSelector(input: LanguageSelector.Input) + func show(alert: UIAlertController) func openMarketplaceScreen() + func openTransfersHistoty(for profile: Profile) } //MARK: Presenter - @@ -126,8 +128,10 @@ protocol MessagePresenterProtocol: BasePresenterProtocol, func hasRunningCall() -> Bool func hasCallInProgress() -> Bool func rejoinRunningCall() + func openMarketplaceScreen() func currentMembersCount() -> UInt + func openTransfersHistoty() } //MARK: Interactor - diff --git a/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift b/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift index 358762512..e8c23e695 100644 --- a/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift +++ b/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift @@ -48,6 +48,10 @@ extension MessageVC: NynjaContextMenuDelegate { break case .marketPlace: marketPlace(menu: contextMenu, userInfo: userInfo) + case .transfers: + showTransfersHistory(menu: contextMenu, userInfo: userInfo) + case .showInBlockchain: + break } } @@ -165,4 +169,8 @@ extension MessageVC: NynjaContextMenuDelegate { private func marketPlace(menu: NynjaContextMenu, userInfo: NynjaContextMenuUserInfo?) { self.presenter.openMarketplaceScreen() } + + private func showTransfersHistory(menu: NynjaContextMenu, userInfo: NynjaContextMenuUserInfo?) { + presenter.openTransfersHistoty() + } } diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift index 3de18d118..db8d687e5 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift @@ -273,7 +273,7 @@ class BaseChatCell: UICollectionViewCell, MessageBaseImageViewDataSource, Messag setupInfoView(model) setupFooterContent(model) - if model.type == .payment { + if model.type == .transfer { hideNetworkProcessingUI() } diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/MessageCellFactory.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/MessageCellFactory.swift index 5079efc7b..c0392667e 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/MessageCellFactory.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/MessageCellFactory.swift @@ -96,7 +96,7 @@ class MessageCellFactory { // MARK: - Helpers private static let types: [SendMessageType] = [ - .contact, .location, .place, .text, .image, .sticker, .file, .video, .audio, .payment, .audioCall + .contact, .location, .place, .text, .image, .sticker, .file, .video, .audio, .transfer, .audioCall ] private static func ids(for ownerType: OwnerType) -> [String] { diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift index 6761b4f39..656a7e959 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/BaseChatCellModel.swift @@ -153,6 +153,8 @@ final class BaseChatCellModel: AudioPlayable { var image: UIImage? var imageSize = CGSize() + + var transferNotes: String? var progressModel: ProgressModel? { didSet { diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Base/MessageViewFactory.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Base/MessageViewFactory.swift index 98e8ec1b5..28183a6da 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Base/MessageViewFactory.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Base/MessageViewFactory.swift @@ -118,8 +118,8 @@ final class MessageViewFactory { content = MessageVideoView(contentAppearance: dependency, dataSource: dependency) case .audio: content = MessageVoiceView(contentAppearance: dependency, delegate: dependency) - case .payment: - content = MessagePaymentView(contentAppearance: dependency) + case .transfer: + content = MessageTransferView(contentAppearance: dependency) case .audioCall: content = MessageCallView(contentAppearance: dependency) default: @@ -151,8 +151,8 @@ final class MessageViewFactory { size = MessageVideoView.size(for: model) case .audio: size = MessageVoiceView.size(for: model) - case .payment: - size = MessagePaymentView.size(for: model) + case .transfer: + size = MessageTransferView.size(for: model) case .audioCall: size = MessageCallView.size(for: model) default: diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Info/InfoView.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Info/InfoView.swift index fa0a47a8d..b15d94a3b 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Info/InfoView.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Info/InfoView.swift @@ -147,7 +147,7 @@ class InfoView: BaseView, InfoInjectible { } private func adjustBackground(_ model: BaseChatCellModel) { - let types: [SendMessageType] = [.contact, .text, .audio, .file, .place, .audioCall, .payment] + let types: [SendMessageType] = [.contact, .text, .audio, .file, .place, .audioCall, .transfer] var replyAppearance: CountAppearanceModel diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessagePaymentView.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessagePaymentView.swift deleted file mode 100644 index 95bf69fea..000000000 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessagePaymentView.swift +++ /dev/null @@ -1,173 +0,0 @@ -// -// MessagePaymentView.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/24/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -class MessagePaymentView: MessageContentView { - - private static let textFont = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: Constraints.textView.labelHeight)! - private static let nynLocaleFont = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: Constraints.textView.nynLocaleHeight)! - - override var coloredViews: [UIView] { - return [self, textView] - } - - class var textViewBottomInset: CGFloat { - return CGFloat(30.0.adjustedByWidth) - } - - var textViewEdges: UIEdgeInsets { - return UIEdgeInsets(top: Constraints.textView.topInset, - left: Constraints.textView.letftInset + Constraints.paymentDirectionImage.sideSize, - bottom: MessagePaymentView.textViewBottomInset, - right: Constraints.textView.rightInset) - } - - // MARK: - Constraints - struct Constraints { - - static let width = BaseChatCell.Constraints.bubble.maxWidth - static let height = CGFloat(67.adjustedByWidth) - - struct textView { - static let topInset = CGFloat(4.0.adjustedByWidth) - static let letftInset = CGFloat(8.0.adjustedByWidth) - static let rightInset = CGFloat(20.0.adjustedByWidth) - static let bottomInset = CGFloat(21.0.adjustedByWidth) - - static let labelHeight = CGFloat(34.0.adjustedByWidth) - static let nynLocaleHeight = CGFloat(24.0.adjustedByWidth) - static let minWidth = CGFloat(40) - } - - struct infoView { - static let inset = CGPoint(x: 0, y: 2.0.adjustedByWidth) - } - - struct paymentDirectionImage { - static let sideSize = CGFloat(28.adjustedByWidth) - static let horizontalInset = CGFloat(5.adjustedByWidth) - static let topInset = CGFloat(10.0.adjustedByWidth) - } - } - - // MARK: - Views - lazy var textView: UITextView = { - let v = UITextView() - - v.isUserInteractionEnabled = true - v.isScrollEnabled = false - v.isEditable = false - v.isSelectable = false - - v.backgroundColor = UIColor.nynja.clear - v.textContainerInset = .zero - v.textContainer.lineFragmentPadding = 0 - - v.linkTextAttributes = [NSAttributedStringKey.foregroundColor.rawValue:UIColor.nynja.blue, - NSAttributedStringKey.underlineColor.rawValue: UIColor.nynja.blue, - NSAttributedStringKey.underlineStyle.rawValue: NSUnderlineStyle.styleSingle.rawValue - ] as [String : Any] - - self.addSubview(v) - v.snp.makeConstraints { make in - make.edges.equalTo(textViewEdges) - make.width.height.equalTo(0) - } - return v - }() - - lazy var paymentDirectionImageView: UIImageView = { - let imageView = UIImageView() - - imageView.contentMode = .scaleAspectFill - let sideSize = Constraints.paymentDirectionImage.sideSize - let verticalInset = Constraints.paymentDirectionImage.topInset - - self.addSubview(imageView) - imageView.snp.makeConstraints { make in - make.width.height.equalTo(sideSize) - make.centerY.equalTo(self.textView.snp.centerY) - make.left.equalTo(Constraints.paymentDirectionImage.horizontalInset) - } - - return imageView - }() - - // MARK: - BaseView - override func baseSetup() { - textView.textContainerInset = .zero - } - - // MARK: - BubbleInjectible - override func configure(with model: BaseChatCellModel) { - super.configure(with: model) - - guard model.type == .payment else { return } - - textView.textColor = UIColor.nynja.darkLight - textView.attributedText = prepareText(in: model) - textView.accessibilityIdentifier = model.text - - let size = textSize(in: model) - textView.snp.updateConstraints { $0.size.equalTo(size) } - - var paymentDirectionImage: UIImage? { - if model.isOwner { - return UIImage.nynja.Wallet.icArrowTransferUp.image - } - return UIImage.nynja.Wallet.icArrowTransferDown.image - } - paymentDirectionImageView.image = paymentDirectionImage - } - - class func size(for model: BaseChatCellModel) -> CGSize? { - return CGSize(width: Constraints.width, height: Constraints.height) - } - - override var infoInset: CGPoint { - return Constraints.infoView.inset - } - - // MARK: - Setup - - private func prepareText(in model: BaseChatCellModel) -> NSAttributedString { - let amountText = model.text ?? "" - let localeText = NYN.code - let resultText = "\(amountText) \(localeText)" - - let font = MessagePaymentView.textFont - let nynFont = MessagePaymentView.nynLocaleFont - let attributedText = NSMutableAttributedString(string: resultText, attributes: [.font: nynFont]) - - let amountRange = NSMakeRange(0, amountText.count) - attributedText.setAttributes([.font: font], range: amountRange) - - return attributedText - } - - private func textSize(in model: BaseChatCellModel) -> CGSize { - - let insets = Constraints.textView.letftInset + Constraints.textView.rightInset - - let maxWidth = BaseChatCell.Constraints.bubble.maxWidth - insets - var minWidth: CGFloat = InfoView.width(for: model) - Constraints.textView.rightInset - - if let headerWidth = BaseChatCell.calculateSenderHeaderSize(for: model)?.width { - minWidth = headerWidth < maxWidth && headerWidth > minWidth ? headerWidth : minWidth - } - - let text = prepareText(in: model) - var textSize = text.boundingRect(with: CGSize(width: maxWidth, height: 0), - options: .usesLineFragmentOrigin, - context: nil).size - - textSize.width = ceil(min(maxWidth, max(textSize.width, minWidth))) - textSize.height = ceil(textSize.height) - - return textSize - } -} diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessageTransferView.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessageTransferView.swift new file mode 100644 index 000000000..e70c48a96 --- /dev/null +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/Views/Message/MessageTransferView.swift @@ -0,0 +1,209 @@ +// +// MessageTransferView.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +final class MessageTransferView: MessageContentView { + + override var coloredViews: [UIView] { + return [self, textView] + } + + var textViewEdges: UIEdgeInsets { + return UIEdgeInsets( + top: Constraints.textView.topInset, + left: Constraints.textView.horizontalInset, + bottom: Constraints.textView.bottomInset, + right: Constraints.textView.horizontalInset) + } + + + // MARK: - Views + lazy var textView: UITextView = { + let textView = UITextView() + + textView.isUserInteractionEnabled = true + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + + addSubview(textView) + + textView.snp.makeConstraints { make in + make.edges.equalTo(textViewEdges) + make.width.height.equalTo(0) + } + return textView + }() + + // MARK: - BaseView + override func baseSetup() { + textView.textContainerInset = .zero + } + + // MARK: - BubbleInjectible + override func configure(with model: BaseChatCellModel) { + super.configure(with: model) + + guard model.type == .transfer else { return } + + textView.attributedText = MessageTransferView.prepareText(in: model) + + let size = MessageTransferView.textSize(in: model) + textView.snp.updateConstraints { $0.size.equalTo(size) } + } + + class func size(for model: BaseChatCellModel) -> CGSize? { + var size = textSize(in: model) + size.height += Constraints.textView.topInset + Constraints.textView.bottomInset + return CGSize(width: Constraints.maxWidth, height: size.height) + } + + override var infoInset: CGPoint { + return Constraints.infoView.inset + } + + private static func makeAttributedNotes(for model: BaseChatCellModel) -> NSAttributedString? { + guard let text = model.transferNotes else { return nil } + let font = MessageTransferView.notesFont + return NSMutableAttributedString(string: text, attributes: [.font: font]) + } + + private static func makeAttributedAmout(for model: BaseChatCellModel) -> NSAttributedString { + let text = model.text ?? "" + let font = MessageTransferView.textFont + return NSMutableAttributedString(string: text, attributes: [.font: font]) + } + + private static func makeAttributedCurrency(for model: BaseChatCellModel) -> NSAttributedString { + let text = NYN.code + let font = MessageTransferView.currencyCodeFont + return NSMutableAttributedString(string: text, attributes: [.font: font]) + } + + private static let attributedSpace: NSAttributedString = { + let font = MessageTransferView.textFont + return NSMutableAttributedString(string: " ", attributes: [.font: font]) + }() + + private static let attributedNewLine: NSAttributedString = { + let font = MessageTransferView.currencyCodeFont + return NSMutableAttributedString(string: "\n", attributes: [.font: font]) + }() + + private static let attributedTransferArrowDownString = makeAttributedIcon(with: UIImage.nynja.Wallet.icArrowTransferDown.image) + private static let attributedTransferArrowUpString = makeAttributedIcon(with: UIImage.nynja.Wallet.icArrowTransferUp.image) + + private static func makeAttributedIcon(with iconImage: UIImage?) -> NSAttributedString? { + guard let iconImage = iconImage else { + return nil + } + + let font = MessageTransferView.textFont + let attachment = NSTextAttachment() + attachment.bounds = CGRect( + x: 0, + y: (font.capHeight - iconImage.size.height).rounded() / 2, + width: iconImage.size.width, + height: iconImage.size.height) + attachment.image = iconImage + return NSAttributedString(attachment: attachment) + } + + private static func attributedIcon(for model: BaseChatCellModel) -> NSAttributedString? { + if model.isOwner { + return attributedTransferArrowUpString + } + return attributedTransferArrowDownString + } + + private static func prepareText(in model: BaseChatCellModel) -> NSAttributedString { + + let finalString = NSMutableAttributedString() + + if let attributedIcon = attributedIcon(for: model) { + finalString.append(attributedIcon) + finalString.append(attributedSpace) + } + + let attributedAmout = makeAttributedAmout(for: model) + finalString.append(attributedAmout) + + let attributedCurrency = makeAttributedCurrency(for: model) + finalString.append(attributedCurrency) + + if let attributedNotes = makeAttributedNotes(for: model) { + finalString.append(attributedNewLine) + finalString.append(attributedNotes) + } + + return finalString + } + + private static func textSize(in model: BaseChatCellModel) -> CGSize { + + let insets = 2 * Constraints.textView.horizontalInset + + let maxWidth = Constraints.maxWidth - insets + var minWidth: CGFloat = InfoViewFactory.width(for: model) - Constraints.textView.horizontalInset + + if let headerWidth = BaseChatCell.calculateSenderHeaderSize(for: model)?.width { + minWidth = minWidth...maxWidth ~= headerWidth ? headerWidth : minWidth + } + let text = prepareText(in: model) + var textSize = text.boundingRect( + with: CGSize(width: maxWidth, height: 0), + options: .usesLineFragmentOrigin, + context: nil).size + + textSize.width = ceil(min(maxWidth, max(textSize.width, minWidth))) + textSize.height = ceil(textSize.height) + + return textSize + } +} + +// MARK: - Constraints +private extension MessageTransferView { + + enum Constraints { + + static let maxWidth = BaseChatCell.Constraints.bubble.maxWidth + static let height = CGFloat(80.adjustedByWidth) + + enum textView { + static let topInset = CGFloat(4.0.adjustedByWidth) + static let horizontalInset = CGFloat(8.0.adjustedByWidth) + static let bottomInset = CGFloat(30.0.adjustedByWidth) + + static let labelFontHeight = CGFloat(34.0.adjustedByWidth) + static let currencyCodeFontHeight = CGFloat(24.0.adjustedByWidth) + static let fontHeight = CGFloat(20.0.adjustedByWidth) + static let minWidth = CGFloat(40) + } + + enum infoView { + static let inset = CGPoint(x: 0, y: 2.0.adjustedByWidth) + } + } +} + +// MARK: - Static fonts +private extension MessageTransferView { + private static let textFont = UIFont.makeFont( + with: FontFamily.NotoSans.regular.name, + height: Constraints.textView.labelFontHeight)! + + private static let currencyCodeFont = UIFont.makeFont( + with: FontFamily.NotoSans.regular.name, + height: Constraints.textView.currencyCodeFontHeight)! + + private static let notesFont = UIFont.makeFont( + with: FontFamily.NotoSans.regular.name, + height: Constraints.textView.fontHeight)! +} diff --git a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift index bb78f4530..dcebcf6c4 100644 --- a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift +++ b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift @@ -152,9 +152,13 @@ class MessageWireFrame: MessageWireframeProtocol, DocumentInteractionWireFrame { func show(alert: UIAlertController) { navigation?.present(alert, animated: true, completion: nil) } - + func openMarketplaceScreen() { main?.openMarketplace() } - + + func openTransfersHistoty(for profile: Profile) { + guard let navigation = self.navigation else { return } + TransferHistoryWireFrame().presentTransferHistory(navigation: navigation, for: profile) + } } diff --git a/Nynja/Modules/Payment/Interactor/PaymentInteractor.swift b/Nynja/Modules/Payment/Interactor/PaymentInteractor.swift deleted file mode 100644 index ebe0da2e3..000000000 --- a/Nynja/Modules/Payment/Interactor/PaymentInteractor.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// PaymentInteractor.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/20/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import Foundation - -final class PaymentInteractor: PaymentInteractorInputProtocol, SetInjectable { - - weak var presenter: PaymentInteractorOutputProtocol! - private var recepientContact: Contact! - private var walletService: WalletServiceProtocol! - private var messageSendingService: MessageSendingServiceProtocol! - private var messageFactory: MessageFactoryProtocol! - - struct Model { - let balance: NYNMoney - let recepientName: String - let maxDigits: Int - } - - private let balance: NYNMoney? = { - guard let profile = ProfileDAO.currentProfile else { - assertionFailure("Profile can't be nil") - return nil - } - - guard let _ = ProfileServices(profile: profile).wallet else { - assertionFailure("Wallet service can't be nil") - return nil - } - - return profile.nynBalance - }() - - private var amount: NYNMoney? = nil - - // MARK: - PaymentInteractorInputProtocol - func setup() { - guard let balance = self.balance else { - return - } - - var fullName: String { - if let fullName = recepientContact.fullName { - return fullName - } - assertionFailure("Full Name can't be construct") - return "" - } - - let model = PaymentInteractor.Model(balance: balance, recepientName: fullName, maxDigits: 6) - - presenter.setup(with: model) - } - - func amountChanged(_ input: String) -> InputInfo? { - switch validate(input) { - case .invalid(let description): - self.amount = nil - return InputInfo(text: description, kind: .warning) - case .valid(let amount): - self.amount = amount - return nil - } - } - - func sendTriggered() { - guard let amount = self.amount, let profile = ProfileDAO.currentProfile else { - assertionFailure("Amount or porfile is nil") - return - } - - presenter.showProgressIndicator() - walletService.sendCoins(amount: amount, recepientСontact: recepientContact, senderProfile: profile) - { error in - dispatchAsyncMain { [weak self] in - if let error = error { - self?.presenter.showAlert(error.localizedDescription) - } else { - self?.sendMessage() - self?.presenter.back() - } - self?.presenter.hideProgressIndicator() - } - } - } - - private func sendMessage() { - guard let amount = self.amount else { - assertionFailure("Amount is nil") - return - } - - let message = messageFactory.makePaymentMessage(inputText: .init(amount), contact: recepientContact) - messageSendingService.sendMessage(message) - } -} - -// MARK: - SetInjectable - -extension PaymentInteractor { - struct Dependencies { - let presenter: PaymentInteractorOutputProtocol - let recepientContact: Contact - let walletService: WalletServiceProtocol - let messageSendingService: MessageSendingServiceProtocol - let messageFactory: MessageFactoryProtocol - } - - func inject(dependencies: PaymentInteractor.Dependencies) { - presenter = dependencies.presenter - recepientContact = dependencies.recepientContact - walletService = dependencies.walletService - messageSendingService = dependencies.messageSendingService - messageFactory = dependencies.messageFactory - } -} - -extension PaymentInteractor { - fileprivate enum AmountValidationState { - case valid(amount: NYNMoney) - case invalid(description: String) - } - - fileprivate func validate(_ input: String) -> AmountValidationState { - guard input.count > 0 else { - return .invalid(description: "") - } - - guard let balance = balance else { - return .invalid(description: "") - } - - switch (NYNMoney(input)) { - case .none: - return .invalid(description: "") - case .some(let amount) where amount > balance: - return .invalid(description: "wallet_alert".localized) - case (0): - return .invalid(description: "") - case .some(let amount): - return .valid(amount: amount) - } - } -} - diff --git a/Nynja/Modules/Payment/PaymentProtocols.swift b/Nynja/Modules/Payment/PaymentProtocols.swift deleted file mode 100644 index be39f4452..000000000 --- a/Nynja/Modules/Payment/PaymentProtocols.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// PaymentProtocols.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/20/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import UIKit - -protocol PaymentWireFrameProtocol: class { - - func presentPayment(navigation: UINavigationController, contact: Contact) - func close() -} - -protocol PaymentViewProtocol: class { - - var presenter: PaymentPresenterProtocol! { get set } - - func setup(with model: PaymentViewController.Model) - func showAlert(_ text: String) -} - -protocol PaymentPresenterProtocol: BasePresenterProtocol, NavigationProtocol { - - var view: PaymentViewProtocol! { get set } - var interactor: PaymentInteractorInputProtocol! { get set } - var wireFrame: PaymentWireFrameProtocol! { get set } - - func showed() - func amountChanged(_ input: String) -> InputInfo? - func sendTriggered() -} - -protocol PaymentInteractorOutputProtocol: class { - - func setup(with model: PaymentInteractor.Model) - func showProgressIndicator() - func hideProgressIndicator() - func back() - func showAlert(_ text: String) -} - -protocol PaymentInteractorInputProtocol: class { - - var presenter: PaymentInteractorOutputProtocol! { get set } - - func setup() - func amountChanged(_ input: String) -> InputInfo? - func sendTriggered() -} diff --git a/Nynja/Modules/Payment/Presenter/PaymentPresenter.swift b/Nynja/Modules/Payment/Presenter/PaymentPresenter.swift deleted file mode 100644 index 2b69e3a11..000000000 --- a/Nynja/Modules/Payment/Presenter/PaymentPresenter.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// PaymentPresenter.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/20/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import Foundation - -final class PaymentPresenter: BasePresenter, PaymentPresenterProtocol, PaymentInteractorOutputProtocol, SetInjectable { - - weak var view: PaymentViewProtocol! - var interactor: PaymentInteractorInputProtocol! - var wireFrame: PaymentWireFrameProtocol! - - struct Dependencies { - let view: PaymentViewProtocol - let interactor: PaymentInteractorInputProtocol - let wireFrame: PaymentWireFrameProtocol - } - - func inject(dependencies: PaymentPresenter.Dependencies) { - view = dependencies.view - interactor = dependencies.interactor - wireFrame = dependencies.wireFrame - } - - // MARK: NavigationProtocol - func back() { - wireFrame.close() - } - - func showAlert(_ text: String) { - view.showAlert(text) - } - - // MARK: PaymentInteractorOutputProtocol - func setup(with model: PaymentInteractor.Model) { - - let formattedBalance = model.balance.formattedCurrency ?? model.balance.description - - let viewModel = PaymentViewController.Model(cells: - [.userData(title: "wallet_recepient".localized, body: model.recepientName), - .userData(title: "wallet_balance".localized, body:formattedBalance), - .amount(title: "wallet_amount".localized, maxDigits: model.maxDigits)]) - - view.setup(with: viewModel) - } - - func showProgressIndicator() { - dispatchAsyncMain { [weak self] in - let baseVC = self?.view as? BaseVC - baseVC?.showSpinner() - } - } - - func hideProgressIndicator() { - dispatchAsyncMain { [weak self] in - let baseVC = self?.view as? BaseVC - baseVC?.hideSpinner() - } - } - - // MARK: - PaymentPresenterProtocol - - func showed() { - interactor.setup() - } - - func amountChanged(_ input: String) -> InputInfo? { - return interactor.amountChanged(input) - } - - func sendTriggered() { - interactor.sendTriggered() - } -} diff --git a/Nynja/Modules/Payment/View/PaymentViewController.swift b/Nynja/Modules/Payment/View/PaymentViewController.swift deleted file mode 100644 index 0a5985797..000000000 --- a/Nynja/Modules/Payment/View/PaymentViewController.swift +++ /dev/null @@ -1,212 +0,0 @@ -// -// PaymentViewController.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/20/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import UIKit -import SnapKit - -final class PaymentViewController: BaseVC, PaymentViewProtocol, SetInjectable { - - private struct Constraints { - struct tableView { - static let separatorInset = CGFloat(16.0.adjustedByWidth) - - } - } - - struct Dependencies { - let presenter: PaymentPresenterProtocol - } - - func inject(dependencies: PaymentViewController.Dependencies) { - presenter = dependencies.presenter - } - - var presenter: PaymentPresenterProtocol! { - didSet { - _presenter = presenter - } - } - - struct Model { - let cells: [PaymentTableViewCell.Model] - } - - //MARK: PaymentViewProtocol - func setup(with model: PaymentViewController.Model) { - self.model = model - tableView.reloadData() - view.bringSubview(toFront: sendButton) - } - - func showAlert(_ text: String) { - AlertManager.sharedInstance.showAlertOk(message: text) - } - - fileprivate var model: PaymentViewController.Model = .init(cells: [PaymentTableViewCell.Model]()) - - // MARK: - Life Cycle - - override func initialize() { - super.initialize() - setupUI() - setupTestingKeysInSubviews() - presenter.showed() - } - - - private func setupNavigationView() { - shouldShowSeparator = true - screenTitle = "wheel_item_transfer".localized.uppercased() - - navigationView.configure(config: NavigationView.Config( - isVisibleSeparator: shouldShowSeparator, - isVisibleBackButton: true, - title: screenTitle, - navigationHandler: presenter, - backButtonImage: UIImage.nynja.cancel.image)) - } - - - // MARK: - UI Setup - - private func setupUI() { - setupNavigationView() - sendButton.isHidden = false - sendButton.isEnabled = false - } - - lazy var sendButton: NynjaButton = { - let button = NynjaButton() - - button.setTitle("wallet_send".localized.uppercased(), for: .normal) - button.addTarget(self, action: #selector(sendTapped), for: .touchUpInside) - - let horizontalInset = 16.0.adjustedByWidth - - self.view.addSubview(button) - button.snp.makeConstraints { make in - make.height.equalTo(44.0.adjustedByWidth) - make.left.right.equalToSuperview().inset(horizontalInset) - make.bottom.equalTo(self.view.keyboardLayoutGuide.snp.top).offset(-28.0.adjustedByWidth) - } - - return button - }() - - lazy var tableView: UITableView = { - let table = UITableView.default - table.delegate = self - table.dataSource = self - table.backgroundColor = UIColor.nynja.clear - table.separatorStyle = .singleLine - table.separatorColor = UIColor.nynja.backgroundGray - table.tableFooterView = UIView() - table.clipsToBounds = true - - table.register(PaymentTableViewCell.self, forCellReuseIdentifier: PaymentTableViewCell.cellId) - table.register(PaymentTableViewAmountCell.self, forCellReuseIdentifier: PaymentTableViewAmountCell.cellId) - - self.view.addSubview(table) - table.snp.makeConstraints({ (make) in - make.top.equalTo(navigationView.snp.bottom) - make.left.right.bottom.equalToSuperview() - make.bottom.equalToSuperview() - }) - - return table - }() - - //MARK: Actions - @objc private func sendTapped() { - view.endEditing(true) - presenter.sendTriggered() - } -} - -// MARK: - Testable -extension PaymentViewController: TestableViewControllerProtocol { - - private enum Keys: String { - case identifier = "identifier" - } - - func setupTestingKeys() { - - } -} - -extension PaymentViewController: UITableViewDelegate, UITableViewDataSource { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.cells.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cellModel = model.cells[indexPath.row] - - var cellOptional: UITableViewCell? { - switch cellModel { - case .userData(let title, let body): - let cell = tableView.dequeueReusableCell(withIdentifier: PaymentTableViewCell.cellId, - for: indexPath) as? PaymentTableViewCell - cell?.titleLabel.text = title - cell?.bodyLabel.text = body - cell?.isUserInteractionEnabled = false - return cell - case .amount(let title, let maxDigits): - let cell = tableView.dequeueReusableCell(withIdentifier: PaymentTableViewAmountCell.cellId, - for: indexPath) as? PaymentTableViewAmountCell - - cell?.textField.placeholder = title - cell?.textField.textChanged = { input in - let info = self.presenter.amountChanged(input.text) - cell?.textField.info = info - self.sendButton.isEnabled = (info == nil) - } - - cell?.textField.shouldTextChanged = { (input, range, string) in - - let modificatedText = (input.text as NSString).replacingCharacters(in: range, with: string) - return modificatedText.count <= maxDigits - } - - return cell - } - } - - guard let cell = cellOptional else { - assertionFailure("Cell should be constuct") - return UITableViewCell(frame: CGRect.zero) - } - - cell.backgroundColor = UIColor.nynja.clear - cell.selectionStyle = .none - - return cell - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let cellModel = model.cells[indexPath.row] - return cellModel.height - } - - func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - let cellModel = model.cells[indexPath.row] - - var edgeInsets: UIEdgeInsets { - switch cellModel { - case .userData: return UIEdgeInsetsMake(0, - Constraints.tableView.separatorInset, - 0, - Constraints.tableView.separatorInset) - case .amount: return UIEdgeInsetsMake(0, cell.bounds.size.width, 0, 0) - } - } - - cell.separatorInset = edgeInsets - } -} diff --git a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift index 7fd91be64..0b73d2bbf 100644 --- a/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift +++ b/Nynja/Modules/Profile/Interactor/ProfileInteractor.swift @@ -25,7 +25,6 @@ class ProfileInteractor: BaseInteractor, ProfileInteractorInputProtocol { private var mqttService: MQTTService! //MARK: - BaseInteractor - override var subscribes: [SubscribeType]? { return [.contact(StorageService.sharedInstance.phoneId!), .contact(nil), .room(nil), .star(nil), .job(nil), .profile] } diff --git a/Nynja/Modules/Profile/Presenter/ProfilePresenter.swift b/Nynja/Modules/Profile/Presenter/ProfilePresenter.swift index c8490c930..aa9f011e7 100644 --- a/Nynja/Modules/Profile/Presenter/ProfilePresenter.swift +++ b/Nynja/Modules/Profile/Presenter/ProfilePresenter.swift @@ -11,20 +11,20 @@ class ProfilePresenter: BasePresenter, ProfilePresenterProtocol, ProfileInteract var contact: Contact { return interactor.myContact } - + var elemsInSection: Int { return 5 } - + //MARK: - BasePresenter - + override var itemsFactory: WCItemsFactory? { return HomeItemsFactory() } - - + + //MARK: - ProfilePresenterProtocol - + private weak var view: ProfileViewProtocol! private var interactor: ProfileInteractorInputProtocol! { didSet { @@ -33,16 +33,15 @@ class ProfilePresenter: BasePresenter, ProfilePresenterProtocol, ProfileInteract } private var wireFrame: ProfileWireFrameProtocol! - + //MARK: - Actions - func showImagePreview(from imageView: UIImageView) { guard let url = contact.avatarUrl else { - return + return } wireFrame.showImagePreview(imageUrl: url, from: imageView) } - + func showQRGenerator() { wireFrame.showQRGenerator() } @@ -50,7 +49,7 @@ class ProfilePresenter: BasePresenter, ProfilePresenterProtocol, ProfileInteract func walletTriggered() { interactor.walletTriggered() } - + func showChat(with contact: Contact) { wireFrame.showChat(with: contact, message: nil) } @@ -58,27 +57,27 @@ class ProfilePresenter: BasePresenter, ProfilePresenterProtocol, ProfileInteract func showGroupChat(with room: Room) { wireFrame.showGroupChat(with: room, message: nil) } - + func showScheduledMessage(with jobId: Int64) { wireFrame.showScheduledMessage(with: jobId) } - + func showProfile(_ contact: Contact) { wireFrame.showProfile(contact) } - + func showChatList() { wireFrame.showChatList() } - + func showGroupList() { wireFrame.showGroupList() } - + func showHistory() { wireFrame.showHistory() } - + func showFavorites() { wireFrame.showFavorites() } @@ -95,14 +94,14 @@ class ProfilePresenter: BasePresenter, ProfilePresenterProtocol, ProfileInteract guard let msg = star.message, let result = interactor.chatModel(from: msg) else { return } - + if let cont = result as? Contact { self.wireFrame.showChat(with: cont, message: msg) } else if let room = result as? Room { self.wireFrame.showGroupChat(with: room, message: msg) } } - + func senderInfo(for chat: DialogCellModel) -> String? { switch chat { case let chat as Room: @@ -116,7 +115,7 @@ class ProfilePresenter: BasePresenter, ProfilePresenterProtocol, ProfileInteract return nil } } - + // MARK: - ProfileInteractorOutputProtocol func showProgressIndicator() { diff --git a/Nynja/Modules/Profile/ProfileProtocols.swift b/Nynja/Modules/Profile/ProfileProtocols.swift index 8a40ca451..045570511 100644 --- a/Nynja/Modules/Profile/ProfileProtocols.swift +++ b/Nynja/Modules/Profile/ProfileProtocols.swift @@ -9,7 +9,7 @@ import UIKit protocol ProfileWireFrameProtocol: class { - + func presentProfile(navigation: UINavigationController, main: MainWireFrame?) func showImagePreview(imageUrl: URL, from imageView: UIImageView) @@ -24,7 +24,7 @@ protocol ProfileWireFrameProtocol: class { func showChatList() func showGroupList() func showHistory() - + func showFavorites() } @@ -33,7 +33,7 @@ protocol ProfilePresenterProtocol: BasePresenterProtocol { func showImagePreview(from imageView: UIImageView) func showQRGenerator() func walletTriggered() - + func showChat(with contact: Contact) func showGroupChat(with room: Room) func senderInfo(for chat: DialogCellModel) -> String? @@ -43,7 +43,7 @@ protocol ProfilePresenterProtocol: BasePresenterProtocol { func showChatList() func showGroupList() func showHistory() - + func showFavorites() func acceptContact(with phoneId: String) @@ -54,7 +54,7 @@ protocol ProfilePresenterProtocol: BasePresenterProtocol { protocol ProfileInteractorOutputProtocol: class { var elemsInSection: Int { get } - + func contactFetched(_ contact: Contact) func lastEventsFetched(_ lastEvents: [ProfileSectionModel]) func showWallet(for profile: Profile) @@ -69,15 +69,15 @@ protocol ProfileInteractorOutputProtocol: class { protocol ProfileInteractorInputProtocol: BaseInteractorProtocol { var myContact: Contact! { get set } - + func acceptContact(with phoneId: String) func chatModel(from message: Message) -> ChatModel? - func ignore(_ contact: Contact) func walletTriggered() + func ignore(_ contact: Contact) } protocol ProfileViewProtocol: class { - + func setup(contact: Contact) func updateEventList(_ lastEvents: [ProfileSectionModel]) diff --git a/Nynja/Modules/Profile/View/DetailsView/ProfileDetailsView.swift b/Nynja/Modules/Profile/View/DetailsView/ProfileDetailsView.swift index daa422756..f956cc96b 100644 --- a/Nynja/Modules/Profile/View/DetailsView/ProfileDetailsView.swift +++ b/Nynja/Modules/Profile/View/DetailsView/ProfileDetailsView.swift @@ -7,62 +7,63 @@ // class ProfileDetailsView: UIView { - + // MARK: Views lazy var avatarImageView: UIImageView = { let width = Constraints.avatarImageView.width.adjustedByWidth - + let imageView = UIImageView(frame: CGRect(x: 0, y:0, width: width, height: width)) - + imageView.contentMode = .scaleAspectFill imageView.roundImageView() - + self.addSubview(imageView) imageView.snp.makeConstraints({ (make) in make.width.height.equalTo(width) make.top.equalToSuperview().offset(Constraints.avatarImageView.topInset.adjustedByHeight) make.left.equalToSuperview().offset(Constraints.avatarImageView.leftInset.adjustedByWidth) }) - + return imageView }() - + lazy var infoView: UIView = { let view = UIView() - + self.addSubview(view) view.snp.makeConstraints({ (make) in make.top.equalTo(avatarImageView.snp.top) - make.left.equalTo(avatarImageView.snp.right).offset(Constraints.infoView.leftInset.adjustedByWidth) + make.left.equalTo(avatarImageView.snp.right).offset( + Constraints.infoView.leftInset.adjustedByWidth) }) - + return view }() - + lazy var nameLabel: UILabel = { let width = Constraints.nameLabel.width.adjustedByWidth let height = width * Constraints.nameLabel.heightProportion let label = UILabel(size: CGSize(width: width, height: height), color: UIColor.nynja.mainRed, fontName: FontFamily.NotoSans.regular.name) - + infoView.addSubview(label) label.snp.makeConstraints({ (make) in make.height.equalTo(height) - + make.left.right.equalToSuperview() make.top.equalToSuperview().offset(-Constraints.nameLabel.topInset.adjustedByHeight) }) - + return label }() - + lazy var surnameLabel: UILabel = { let nameWidth = Constraints.nameLabel.width.adjustedByWidth let height = nameWidth * Constraints.nameLabel.heightProportion let label = UILabel(size: CGSize(width: nameWidth, height: height), color: UIColor.nynja.mainRed, fontName: FontFamily.NotoSans.regular.name) - + infoView.addSubview(label) label.snp.makeConstraints({ (make) in make.height.equalTo(height) @@ -115,7 +116,7 @@ class ProfileDetailsView: UIView { lazy var balanceLabel: UILabel = { let width = Constraints.balanceLabel.width.adjustedByWidth let height = width * Constraints.balanceLabel.heightProportion - + let label = UILabel( size: CGSize(width: width, height: height), color: UIColor.nynja.white, diff --git a/Nynja/Modules/Profile/View/ProfileViewController.swift b/Nynja/Modules/Profile/View/ProfileViewController.swift index 54a69db17..713962962 100644 --- a/Nynja/Modules/Profile/View/ProfileViewController.swift +++ b/Nynja/Modules/Profile/View/ProfileViewController.swift @@ -10,7 +10,7 @@ import UIKit import NynjaUIKit class ProfileViewController: BaseVC, ProfileViewProtocol { - + private var presenter: ProfilePresenterProtocol! { didSet { _presenter = presenter @@ -28,7 +28,7 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { private lazy var detailsView: ProfileDetailsView = { let height = Constraints.detailsView.height.adjustedByWidth let width = self.view.bounds.width - + let detailsView = ProfileDetailsView(frame: CGRect(x: 0, y: 0, width: width, height: height)) detailsView.backgroundColor = UIColor.nynja.clear @@ -53,12 +53,12 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { // MARK: - Life Cycle - + override func initialize() { super.initialize() setupUI() } - + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() @@ -70,12 +70,12 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { // MARK: - Setup UI - + private func setupUI() { setupTableView() setupDetailsView() } - + private func setupTableView() { tableView.register(viewModel: ChatListMessageCellModel.self) tableView.register(ProfileContactCell.self, forCellReuseIdentifier: ProfileContactCell.cellId) @@ -83,7 +83,7 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { tableView.register(ProfileActionCell.self, forCellReuseIdentifier: ProfileActionCell.cellId) tableView.register(ProfilePlaceholderCell.self, forCellReuseIdentifier: ProfilePlaceholderCell.cellId) tableView.register(StarMessageCell.self, forCellReuseIdentifier: StarMessageCell.cellId) - + tableViewDelegate = ProfileTableViewDelegate(sectionDelegate: self) tableViewDS = ProfileTablewViewDS(payloadParser: payloadParser, contactCellDelegate: self, chatCellDelegate: self) @@ -92,7 +92,7 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { tableView.tableHeaderView = detailsView } - + private func setupDetailsView() { detailsView.isHidden = false @@ -101,20 +101,21 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { detailsView.avatarImageView.isUserInteractionEnabled = true let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(avatarTapped)) detailsView.avatarImageView.addGestureRecognizer(tapRecognizer) - detailsView.walletButton.addTarget(self, action: #selector(walletTapped), for: .touchUpInside) + + detailsView.addTargetForWalletButton(self, selector: #selector(walletButtonAction)) } - - // MARK: - ProfileViewProtocol - + + //MARK: - ProfileViewProtocol + func setup(contact: Contact) { detailsView.avatarImageView.setImage(url: contact.avatarUrl, placeHolder: UIImage.nynja.Contacts.avaPlaceholder.image, accessibilityPrefix: "profile_details_view") - + detailsView.nameLabel.text = contact.names detailsView.surnameLabel.text = contact.surnames detailsView.usernameLabel.text = contact.nick - + if let number = contact.plusPhoneNumber { detailsView.phoneLabel.text = number.stringAsPhone() } @@ -123,7 +124,7 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { func showWallet(with balance: NYNMoney?) { detailsView.showWallet(with: balance) } - + func updateEventList(_ lastEvents: [ProfileSectionModel]) { tableViewDS.lastEvents = lastEvents tableView.reloadData() @@ -143,18 +144,17 @@ class ProfileViewController: BaseVC, ProfileViewProtocol { // MARK: - Actions - + @objc private func qrTaped() { presenter.showQRGenerator() } - + @objc private func avatarTapped() { presenter.showImagePreview(from: detailsView.avatarImageView) } - - @objc private func walletTapped() { -// presenter.walletTriggered() - unavailableFunctionality() + + @objc private func walletButtonAction() { + presenter.walletTriggered() } } @@ -167,7 +167,7 @@ extension ProfileViewController: ComingSoonProtocol {} // MARK: - ChatListMessageCellModelDelegate extension ProfileViewController: ChatListMessageCellModelDelegate { - + func chatListCellModel(_ cellModel: ChatListMessageCellModel, senderNameForModel model: DialogCellModel) -> String? { return presenter.senderInfo(for: model) } @@ -203,7 +203,7 @@ extension ProfileViewController: ProfileViewSectionDelegate { func groupMessageTapped(_ room: Room) { presenter.showGroupChat(with: room) } - + func contactTapped(_ contact: Contact) { presenter.showChat(with: contact) } @@ -215,7 +215,7 @@ extension ProfileViewController: ProfileViewSectionDelegate { func starTapped(_ star: Star) { self.presenter.starTapped(star) } - + func showMoreTapped(for profileSection: ProfileSection) { switch profileSection { case .unreadMessages: @@ -230,7 +230,7 @@ extension ProfileViewController: ProfileViewSectionDelegate { break } } - + func actionTapped(_ action: ProfileAction) { switch action { case .starredMessages: diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/CreateWalletProtocols.swift b/Nynja/Modules/Wallet Flows/CreateWallet/CreateWalletProtocols.swift new file mode 100644 index 000000000..9ce974614 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/CreateWalletProtocols.swift @@ -0,0 +1,37 @@ +// +// CreateWalletProtocols.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol CreateWalletWireFrameProtocol: class { + + func presentCreateWallet(navigation: UINavigationController, for profile: Profile) + func close() + func showSeedBackup(with params: CreateWalletParams) +} + +protocol CreateWalletViewProtocol: class { + func setup(with validationResult: CreateWalletValidationViewModel) +} + +protocol CreateWalletPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + + func showed() + func showSeedBackup() + func validateName(with text: String) + func validatePasscodes(passcode: String, passcodeConfirm: String) +} + +protocol CreateWalletInteractorOutputProtocol: class { +} + +protocol CreateWalletInteractorInputProtocol: class { + func getCreateWalletOutParams() -> CreateWalletParams + func validateWalletName(_ input: String) -> CreateWalletState + func validateWalletPasscodes(passcode: String, passcodeConfirm: String) -> CreateWalletState +} diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletParams.swift b/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletParams.swift new file mode 100644 index 000000000..8e99edfe9 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletParams.swift @@ -0,0 +1,13 @@ +// +// CreateWalletParams.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct CreateWalletParams { + let name: String + let passcode: String + let profile: Profile +} diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletState.swift b/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletState.swift new file mode 100644 index 000000000..bae9b04f7 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletState.swift @@ -0,0 +1,33 @@ +// +// CreateWalletState.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct CreateWalletState { + struct Input { + enum ValidationResult { + case valid + case invalid(description: String) + } + + let string: String + let validationRestult: ValidationResult + } + + var name: Input + var passcode: Input + var passcodeConfirm: Input + + var valid: Bool { + guard + case .valid = name.validationRestult, + case .valid = passcode.validationRestult, + case .valid = passcodeConfirm.validationRestult else { + return false + } + return true + } +} diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletValidationViewModel.swift b/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletValidationViewModel.swift new file mode 100644 index 000000000..cbd9056fe --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/Entities/CreateWalletValidationViewModel.swift @@ -0,0 +1,14 @@ +// +// CreateWalletValidationViewModel.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct CreateWalletValidationViewModel { + let name: InputInfo? + let passcode: InputInfo? + let passcodeConfirmation: InputInfo? + let valid: Bool +} diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/Interactor/CreateWalletInteractor.swift b/Nynja/Modules/Wallet Flows/CreateWallet/Interactor/CreateWalletInteractor.swift new file mode 100644 index 000000000..03838dce8 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/Interactor/CreateWalletInteractor.swift @@ -0,0 +1,108 @@ +// +// CreateWalletInteractor.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class CreateWalletInteractor: CreateWalletInteractorInputProtocol, SetInjectable { + + private weak var presenter: CreateWalletInteractorOutputProtocol! + private var profile: Profile! + private var inputValidationService: WalletCreationTextInputValidationServiceProtocol! + + private var state: CreateWalletState = CreateWalletState( + name: CreateWalletState.Input( + string: "", + validationRestult: .invalid(description: "")), + passcode: CreateWalletState.Input( + string: "", + validationRestult: .invalid(description: "")), + passcodeConfirm: CreateWalletState.Input( + string: "", + validationRestult: .invalid(description: ""))) +} + +extension CreateWalletInteractor { + struct Dependencies { + let presenter: CreateWalletInteractorOutputProtocol + let profile: Profile + let inputValidationService: WalletCreationTextInputValidationServiceProtocol + } + + func inject(dependencies: CreateWalletInteractor.Dependencies) { + presenter = dependencies.presenter + profile = dependencies.profile + inputValidationService = dependencies.inputValidationService + } +} + +//MARK: CreateWalletInteractorInputProtocol +extension CreateWalletInteractor { + + func getCreateWalletOutParams() -> CreateWalletParams { + return CreateWalletParams( + name: state.name.string, + passcode: state.passcode.string, + profile: profile) + } + + func validateWalletName(_ input: String) -> CreateWalletState { + + let result = inputValidationService.validateWallet(name: input) + state.name = makeInput(from: result, with: input) + + return state + } + + func validateWalletPasscodes(passcode: String, passcodeConfirm: String) -> CreateWalletState { + + let passcodeResult = inputValidationService.validateWallet(passcode: passcode) + var passcodeConfirmResult = inputValidationService.validateWallet(passcode: passcodeConfirm) + if case .success = passcodeResult, case .success = passcodeConfirmResult { + passcodeConfirmResult = inputValidationService.validateWallet( + passcode: passcode, + confirmPasscode: passcodeConfirm) + } + state.passcode = CreateWalletState.Input( + string: passcode, + validationRestult: map(from: passcodeResult)) + state.passcodeConfirm = CreateWalletState.Input( + string: passcodeConfirm, + validationRestult: map(from: passcodeConfirmResult)) + + return state + } + + private func makeInput(from result: TextInputValidationResult, + with string: String) -> CreateWalletState.Input { + + var validationResult: CreateWalletState.Input.ValidationResult { + switch result { + case .success: + return .valid + case .failure(let error): + return .invalid(description: error.localizedDescription) + } + } + + return CreateWalletState.Input(string: string, validationRestult: validationResult) + } + + private func map(from result: TextInputValidationResult) -> CreateWalletState.Input.ValidationResult { + + var validationResult: CreateWalletState.Input.ValidationResult { + switch result { + case .success: + return .valid + case .failure(let error): + return .invalid(description: error.localizedDescription) + } + } + + return validationResult + } +} diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/Presenter/CreateWalletPresenter.swift b/Nynja/Modules/Wallet Flows/CreateWallet/Presenter/CreateWalletPresenter.swift new file mode 100644 index 000000000..42af69ada --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/Presenter/CreateWalletPresenter.swift @@ -0,0 +1,81 @@ +// +// CreateWalletPresenter.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class CreateWalletPresenter: BasePresenter, CreateWalletPresenterProtocol, CreateWalletInteractorOutputProtocol { + + override var itemsFactory: WCItemsFactory? { + return nil + } + + private weak var view: CreateWalletViewProtocol! + private var interactor: CreateWalletInteractorInputProtocol! + private var wireFrame: CreateWalletWireFrameProtocol! + + // MARK: - CreateWalletPresenterProtocol + func showed() { + + } + + func validateName(with text: String) { + let state = interactor.validateWalletName(text) + let viewModel = map(model: state) + view.setup(with: viewModel) + } + + func validatePasscodes(passcode: String, passcodeConfirm: String) { + let state = interactor.validateWalletPasscodes(passcode: passcode, passcodeConfirm: passcodeConfirm) + let viewModel = map(model: state) + view.setup(with: viewModel) + } + + func showSeedBackup() { + let params = interactor.getCreateWalletOutParams() + wireFrame.showSeedBackup(with: params) + } + + //MARK: NavigationProtocol + func back() { + wireFrame.close() + } +} + +extension CreateWalletPresenter: SetInjectable { + struct Dependencies { + let view: CreateWalletViewProtocol + let interactor: CreateWalletInteractorInputProtocol + let wireFrame: CreateWalletWireFrameProtocol + } + + func inject(dependencies: CreateWalletPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } +} + +private extension CreateWalletPresenter { + + func map(model: CreateWalletState) -> CreateWalletValidationViewModel { + return CreateWalletValidationViewModel( + name: makeInputInfoFrom(model.name.validationRestult), + passcode: makeInputInfoFrom(model.passcode.validationRestult), + passcodeConfirmation: makeInputInfoFrom(model.passcodeConfirm.validationRestult), + valid: model.valid) + } + + func makeInputInfoFrom(_ validationResult: CreateWalletState.Input.ValidationResult) -> InputInfo? { + switch validationResult { + case .invalid(let description): + return InputInfo(text: description, kind: .warning) + case .valid: + return nil + } + } +} diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift b/Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift new file mode 100644 index 000000000..a8604ad8d --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift @@ -0,0 +1,238 @@ +// +// CreateWalletViewController.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class CreateWalletViewController: BaseVC, CreateWalletViewProtocol, TestableViewControllerProtocol { + + private var presenter: CreateWalletPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + // MARK: - Views + + private lazy var walletNameTextField: MaterialTextField = { + let textField = MaterialTextField() + textField.placeholder = "wallet_name".localized + textField.backgroundColor = UIColor.clear + + view.addSubview(textField) + + textField.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom).offset(Consts.walletNameTextField.topOffset) + make.height.equalTo(Consts.walletNameTextField.height) + make.left.right.equalToSuperview().inset(Consts.walletNameTextField.rightInset) + } + + return textField + }() + + lazy var pinTextField: MaterialTextField = { + let textField = MaterialTextField() + textField.placeholder = "wallet_enter_pin".localized + textField.keyboardType = UIKeyboardType.numberPad + textField.isSecureTextEntry = true + textField.backgroundColor = UIColor.clear + + view.addSubview(textField) + + textField.snp.makeConstraints { make in + make.top.equalTo(walletNameTextField.snp.bottom) + make.height.equalTo(Consts.pinTextField.height) + make.left.right.equalToSuperview().inset(Consts.pinTextField.horizontalInset) + } + + return textField + }() + + lazy var pinConfirmTextField: MaterialTextField = { + let textField = MaterialTextField() + textField.placeholder = "wallet_confirm_pin".localized + textField.keyboardType = .numberPad + textField.isSecureTextEntry = true + textField.backgroundColor = UIColor.clear + + view.addSubview(textField) + + textField.snp.makeConstraints { make in + make.top.equalTo(pinTextField.snp.bottom) + make.height.equalTo(Consts.pinConfirmTextField.height) + make.left.right.equalToSuperview().inset(Consts.pinConfirmTextField.horizontalInset) + } + + return textField + }() + + private lazy var emptyLabel: UILabel = { + + let label = UILabel( + height: Consts.emptyLabel.fontHeight, + color: UIColor.nynja.manatee, + fontName: FontFamily.NotoSans.medium.name) + label.text = "wallet_pin_require".localized + label.numberOfLines = Consts.emptyLabel.numberOfLines + label.textAlignment = .left + + view.addSubview(label) + + label.snp.makeConstraints { make in + make.top.equalTo(pinConfirmTextField.snp.bottom) + make.height.equalTo(Consts.emptyLabel.height) + make.left.right.equalToSuperview().inset(Consts.emptyLabel.horizontalInset) + } + + return label + + }() + + private lazy var seedButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("wallet_seed_backup".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(seedButtonAction), for: .touchUpInside) + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(Consts.seedButton.height) + make.left.right.equalToSuperview().inset(Consts.seedButton.horizontalInset) + make.bottom.equalTo(view.keyboardLayoutGuide.snp.top).offset(-Consts.seedButton.bottomOffset) + } + + return button + }() + + //MARK: Actions + @objc private func seedButtonAction() { + view.endEditing(true) + presenter.showSeedBackup() + } + + + // MARK: - Life Cycle + + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + presenter.showed() + } + + + // MARK: - UI Setup + + private func setupUI() { + + shouldShowSeparator = true + screenTitle = "wallet_create".localized.uppercased() + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: shouldShowSeparator, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + + walletNameTextField.isHidden = false + pinTextField.isHidden = false + pinConfirmTextField.isHidden = false + emptyLabel.isHidden = false + seedButton.isHidden = false + seedButton.isEnabled = false + + walletNameTextField.textChanged = { [weak self] input in + self?.nameValidationTriggered() + } + + pinTextField.textChanged = { [weak self] input in + self?.passcodeValidationTriggered() + } + + pinConfirmTextField.textChanged = { [weak self] input in + self?.passcodeValidationTriggered() + } + } +} + +// MARK: - Testable +extension CreateWalletViewController { + func setupTestingKeys() { + walletNameTextField.accessibilityIdentifier = "wallet_name_text_field" + pinTextField.accessibilityIdentifier = "pin_text_field" + pinConfirmTextField.accessibilityIdentifier = "pin_confirm_text_field" + emptyLabel.accessibilityIdentifier = "empty_label" + seedButton.accessibilityIdentifier = "seed_button" + } +} + +extension CreateWalletViewController: SetInjectable { + struct Dependencies { + let presenter: CreateWalletPresenterProtocol + } + + func inject(dependencies: CreateWalletViewController.Dependencies) { + presenter = dependencies.presenter + } +} + +private extension CreateWalletViewController { + func nameValidationTriggered() { + presenter.validateName(with: walletNameTextField.text) + } + + func passcodeValidationTriggered() { + presenter.validatePasscodes(passcode: pinTextField.text , passcodeConfirm: pinConfirmTextField.text) + } +} + +//MARK: CreateWalletViewProtocol +extension CreateWalletViewController { + func setup(with validationResult: CreateWalletValidationViewModel) { + self.walletNameTextField.info = validationResult.name + self.pinTextField.info = validationResult.passcode + self.pinConfirmTextField.info = validationResult.passcodeConfirmation + self.seedButton.isEnabled = validationResult.valid + } +} + +private extension CreateWalletViewController { + + enum Consts { + enum walletNameTextField { + static let topOffset = CGFloat(13.adjustedByWidth) + static let height = CGFloat(65.adjustedByWidth) + static let rightInset = CGFloat(16.0.adjustedByWidth) + } + + enum pinTextField { + static let height = CGFloat(65.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + } + + enum pinConfirmTextField { + static let height = CGFloat(65.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + } + + enum emptyLabel { + static let height = CGFloat(65.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let numberOfLines = 2 + static let fontHeight = CGFloat(20) + } + + enum seedButton { + static let height = CGFloat(44.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let bottomOffset = CGFloat(28.adjustedByWidth) + } + } +} + diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/WireFrame/CreateWalletWireFrame.swift b/Nynja/Modules/Wallet Flows/CreateWallet/WireFrame/CreateWalletWireFrame.swift new file mode 100644 index 000000000..37fa85943 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/CreateWallet/WireFrame/CreateWalletWireFrame.swift @@ -0,0 +1,60 @@ +// +// CreateWalletWireFrame.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class CreateWalletWireFrame: CreateWalletWireFrameProtocol { + + private weak var navigationController: UINavigationController? + + func presentCreateWallet(navigation: UINavigationController, for profile: Profile) { + navigationController = navigation + + let view = CreateWalletViewController() + let presenter = CreateWalletPresenter() + let interactor = CreateWalletInteractor() + + // Module components + let viewDependencies = CreateWalletViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + + let presenterDependencies = CreateWalletPresenter.Dependencies( + view: view, + interactor: interactor, + wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + + let serviceFactory = ServiceFactory() + let validationService = serviceFactory.makeWalletCreationTextInputValidationService() + + let interactorDependencies = CreateWalletInteractor.Dependencies( + presenter: presenter, + profile: profile, + inputValidationService: validationService) + interactor.inject(dependencies: interactorDependencies) + + + navigationController?.pushViewController(view, animated: true) + } + + func close() { + navigationController?.popViewController(animated: true) + } + + func showSeedBackup(with params: CreateWalletParams) { + guard let navigationController = navigationController else { return } + SeedBackupWalletWireFrame().presentSeedBackupWallet( + params: SeedBackupWalletInputParams( + name: params.name, + passcode: params.passcode, + profile: params.profile), + navigation: navigationController) + } +} + + diff --git a/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentModel.swift b/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentModel.swift new file mode 100644 index 000000000..fe13c7def --- /dev/null +++ b/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentModel.swift @@ -0,0 +1,13 @@ +// +// PaymentModel.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct PaymentModel { + let balance: NYNMoney + let recipientName: String + let maxDigits: Int +} diff --git a/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentTableCellModel.swift b/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentTableCellModel.swift new file mode 100644 index 000000000..33674d2d9 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentTableCellModel.swift @@ -0,0 +1,19 @@ +// +// PaymentTableCellModel.swift +// Nynja +// +// Created by Aleksander Pavliuk on 9/4/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct PaymentTableCellModel { + enum CellType { + case userData(body: String) + case amount(changeTextHandler: MTIShouldChangeTextHandler) + case notes + } + + var type: CellType + var height: CGFloat + var title: String +} diff --git a/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentViewControllerModel.swift b/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentViewControllerModel.swift new file mode 100644 index 000000000..5538415bd --- /dev/null +++ b/Nynja/Modules/Wallet Flows/Payment/Entities/PaymentViewControllerModel.swift @@ -0,0 +1,9 @@ +// +// PaymentViewControllerModel.swift +// Nynja +// +// Created by Aleksander Pavliuk on 9/4/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +typealias PaymentViewControllerModel = [PaymentTableCellModel] diff --git a/Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift b/Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift new file mode 100644 index 000000000..db481843c --- /dev/null +++ b/Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift @@ -0,0 +1,215 @@ +// +// PaymentInteractor.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/20/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class PaymentInteractor: PaymentInteractorInputProtocol, SetInjectable { + + private weak var presenter: PaymentInteractorOutputProtocol! + private var recipientContact: Contact! + private var contactServices: ContactServices! + private var walletService: WalletServiceProtocol! + private var messageSendingService: MessageSendingServiceProtocol! + private var messageFactory: MessageFactoryProtocol! + private var profile: Profile! + private var profileServices: ProfileServices! + private var inputValidationService: WalletOpeningTextInputValidationServiceProtocol! + + private var amount: NYNMoney? = nil + private var notes: String? + + private var balance: NYNMoney? + + // MARK: - PaymentInteractorInputProtocol + func amountChanged(_ input: String) -> InputInfo? { + switch validate(input) { + case .invalid(let description): + self.amount = nil + return InputInfo(text: description, kind: .warning) + case .valid(let amount): + self.amount = amount + return nil + } + } + + func notesChanged(_ input: String) { + notes = input + } + + func sendCoins(with passcode: String, completion: @escaping (Error?) -> ()) { + makeTransfer(passcode: passcode) { [weak self] (error) in + guard let `self` = self else { + return + } + + if error == nil { + self.sendMessage() + } + completion(error) + } + } + + func getWalletName() -> String? { + // Wallet inplementation should be changed on server side, until this wallet name will be stored in password field + return profileServices.wallet?.password + } + + func validate(passcode: String) -> Bool { + let result = inputValidationService.validateWallet(passcode: passcode) + switch result { + case .success: + return true + case .failure: + return false + } + } + + func setupWallet(with name: String?, passcode: String, completion: @escaping (Error?) -> ()) { + walletService.getWallet(name: name, passcode: passcode) { [weak self] (result) in + guard let `self` = self else { + return + } + + switch result { + case .success(let walletFromSDK): + var error: Error? + if let amount = Decimal(string: walletFromSDK.account.balance) { + self.balance = NYNMoney(amount) + self.setup() + } else { + error = PaymentInteractorError.amountError + } + completion(error) + case .failure(let error): + completion(error) + } + } + } +} + +// MARK: - SetInjectable + +extension PaymentInteractor { + struct Dependencies { + let presenter: PaymentInteractorOutputProtocol + let recipientContact: Contact + let walletService: WalletServiceProtocol + let messageSendingService: MessageSendingServiceProtocol + let messageFactory: MessageFactoryProtocol + let profile: Profile + let profileServices: ProfileServices + let inputValidationService: WalletOpeningTextInputValidationServiceProtocol + let contactServices: ContactServices + } + + func inject(dependencies: PaymentInteractor.Dependencies) { + presenter = dependencies.presenter + recipientContact = dependencies.recipientContact + walletService = dependencies.walletService + messageSendingService = dependencies.messageSendingService + messageFactory = dependencies.messageFactory + profile = dependencies.profile + profileServices = dependencies.profileServices + inputValidationService = dependencies.inputValidationService + contactServices = dependencies.contactServices + } +} + +private extension PaymentInteractor { + func setup() { + guard let balance = balance else { + return + } + + var fullName: String { + if let fullName = recipientContact.fullName { + return fullName + } + assertionFailure("Full Name can't be construct") + return "" + } + + let model = PaymentModel(balance: balance, recipientName: fullName, maxDigits: 6) + + presenter.setup(with: model) + } + + func sendMessage() { + guard let amount = self.amount else { + assertionFailure("Amount is nil") + return + } + + var notesAdjusted: String? { + guard let notes = notes else { return nil } + let trimmedString = notes.trimmingCharacters(in: .whitespaces) + return trimmedString.isEmpty ? nil : trimmedString + } + + let message = messageFactory.makePaymentMessage( + inputText: String(amount), + contact: recipientContact, + notes: notesAdjusted) + messageSendingService.sendMessage(message) + } + + func makeTransfer(passcode: String, completion: @escaping (Error?) -> ()) { + guard let receipientAddress = contactServices.wallet?.login, let amountString = amount?.description else { + completion(PaymentInteractorError.cantSendCoins) + return + } + + let name = getWalletName() + + walletService.makeTransfer( + name: name, + passcode: passcode, + receipientAddress: receipientAddress, + tockenType: amountString, + amount: amountString) { (result) in + completion(result.error) + } + } +} + +private extension PaymentInteractor { + enum PaymentInteractorError: Error { + case amountError + case cantSendCoins + } +} + +private extension PaymentInteractor { + enum AmountState { + case valid(amount: NYNMoney) + case invalid(description: String) + } + + func validate(_ input: String) -> AmountState { + if input.count <= 0 { + return .invalid(description: "") + } + + guard let balance = balance else { + return .invalid(description: "") + } + + let moneyAmount = NYNMoney(input) + switch (moneyAmount) { + case .none: + return .invalid(description: "") + case .some(let amount) where amount > balance: + return .invalid(description: "wallet_alert".localized) + case (0): + return .invalid(description: "") + case .some(let amount): + return .valid(amount: amount) + } + } +} + diff --git a/Nynja/Modules/Wallet Flows/Payment/PaymentProtocols.swift b/Nynja/Modules/Wallet Flows/Payment/PaymentProtocols.swift new file mode 100644 index 000000000..a8503b458 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/Payment/PaymentProtocols.swift @@ -0,0 +1,48 @@ +// +// PaymentProtocols.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/20/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol PaymentWireFrameProtocol: class { + func presentPayment(navigation: UINavigationController, contact: Contact, profile: Profile) + func close() +} + +protocol PaymentViewProtocol: class { + func hideActivityIndicator() + func showActivityIndicator() + func setup(with model: PaymentViewControllerModel) + func showAlert(_ text: String) + func setupPassocdeInput() +} + +protocol PaymentPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + func showed() + func amountChanged(_ input: String) -> InputInfo? + func sendCoins(with passcode: String) + func setupWallet(with passcode: String) + func notesChanged(_ input: String) + func passcodeAlertTextSholdChange(text: String?, range: NSRange, string: String) -> Bool + func passcodeInputCanceled() +} + +protocol PaymentInteractorOutputProtocol: class { + func setup(with model: PaymentModel) + func showProgressIndicator() + func hideProgressIndicator() + func back() +} + +protocol PaymentInteractorInputProtocol: class { + func setupWallet(with name: String?, passcode: String, completion: @escaping (Error?) -> ()) + func amountChanged(_ input: String) -> InputInfo? + func notesChanged(_ input: String) + func sendCoins(with passcode: String, completion: @escaping (Error?) -> ()) + func getWalletName() -> String? + func validate(passcode: String) -> Bool +} diff --git a/Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift b/Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift new file mode 100644 index 000000000..f9c40bbee --- /dev/null +++ b/Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift @@ -0,0 +1,151 @@ +// +// PaymentPresenter.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/20/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class PaymentPresenter: BasePresenter, SetInjectable, PaymentPresenterProtocol, PaymentInteractorOutputProtocol { + + private weak var view: PaymentViewProtocol! + private var interactor: PaymentInteractorInputProtocol! + private var wireFrame: PaymentWireFrameProtocol! + + // MARK: NavigationProtocol + func back() { + wireFrame.close() + } + + var viewModel: PaymentViewControllerModel! + + // MARK: PaymentInteractorOutputProtocol + + func setup(with model: PaymentModel) { + + let formattedBalance = model.balance.formattedCurrency ?? model.balance.description + + let paymentAmountChangeTextHandler: MTIShouldChangeTextHandler = { (input, range, string) in + let modificatedText = (input.text as NSString).replacingCharacters(in: range, with: string) + return modificatedText.count <= 6 + } + + let recipient = PaymentTableCellModel( + type: .userData(body: model.recipientName), + height: PaymentViewController.Constraints.Cells.userDataCellHeight, + title: "wallet_recipient".localized) + + let balance = PaymentTableCellModel( + type: .userData(body: formattedBalance), + height: PaymentViewController.Constraints.Cells.userDataCellHeight, + title: "wallet_balance".localized) + + let amount = PaymentTableCellModel( + type: .amount(changeTextHandler: paymentAmountChangeTextHandler), + height: PaymentViewController.Constraints.Cells.amountCellHeight, + title: "wallet_amount".localized) + + let notes = PaymentTableCellModel( + type: .notes, + height: PaymentViewController.Constraints.Cells.amountCellHeight, + title: "wallet_notes".localized) + + viewModel = [recipient, balance, amount, notes] + + dispatchAsyncMain { [weak self] in + guard let `self` = self else { + return + } + self.view.setup(with: self.viewModel) + } + } + + func showProgressIndicator() { + dispatchAsyncMain { [weak self] in + let baseVC = self?.view as? BaseVC + baseVC?.showSpinner() + } + } + + func hideProgressIndicator() { + dispatchAsyncMain { [weak self] in + let baseVC = self?.view as? BaseVC + baseVC?.hideSpinner() + } + } + + // MARK: - PaymentPresenterProtocol + func passcodeInputCanceled() { + wireFrame.close() + } + + func passcodeAlertTextSholdChange(text: String?, range: NSRange, string: String) -> Bool { + let result = String.replace(in: text, range: range, string: string) + return interactor.validate(passcode: result) + } + + func showed() { + let name = interactor.getWalletName() + if name != nil { + view.setupPassocdeInput() + } + } + + func amountChanged(_ input: String) -> InputInfo? { + return interactor.amountChanged(input) + } + + func notesChanged(_ input: String) { + interactor.notesChanged(input) + } + + func sendCoins(with passcode: String) { + dispatchAsyncMain { [weak self] in + self?.view.showActivityIndicator() + } + interactor.sendCoins(with: passcode) { [weak self] (error) in + guard let `self` = self else { + return + } + dispatchAsyncMain { + if let error = error { + self.view.showAlert(error.localizedDescription) + } + self.view.hideActivityIndicator() + self.back() + } + } + } + + func setupWallet(with passcode: String) { + dispatchAsyncMain { [weak self] in + self?.view.showActivityIndicator() + } + let name = interactor.getWalletName() + interactor.setupWallet(with: name, passcode: passcode) { [weak self] (error) in + guard let `self` = self else { + return + } + dispatchAsyncMain { + self.view.hideActivityIndicator() + } + } + } +} + +// MARK: - SetInjectable +extension PaymentPresenter { + struct Dependencies { + let view: PaymentViewProtocol + let interactor: PaymentInteractorInputProtocol + let wireFrame: PaymentWireFrameProtocol + } + + func inject(dependencies: PaymentPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } +} diff --git a/Nynja/Modules/Payment/View/PaymentTableViewCell.swift b/Nynja/Modules/Wallet Flows/Payment/View/PaymentTableViewCell.swift similarity index 81% rename from Nynja/Modules/Payment/View/PaymentTableViewCell.swift rename to Nynja/Modules/Wallet Flows/Payment/View/PaymentTableViewCell.swift index 9f2e8d02c..7038c121b 100644 --- a/Nynja/Modules/Payment/View/PaymentTableViewCell.swift +++ b/Nynja/Modules/Wallet Flows/Payment/View/PaymentTableViewCell.swift @@ -1,5 +1,5 @@ // -// PaymentTableViewCell.swift +// PaymentTableViewCells.swift // Nynja // // Created by Aleksandr Pavliuk on 6/20/18. @@ -8,21 +8,9 @@ import Foundation -class PaymentTableViewCell: UITableViewCell { +class PaymentTableViewUserDataCell: UITableViewCell { - enum Model { - case userData(title: String, body: String) - case amount(title: String, maxDigits: Int) - - var height: CGFloat { - switch self { - case .userData: return 64.0 - case .amount: return 80.0 - } - } - } - - static let cellId = "PaymentTableViewCell" + static let cellId = "PaymentTableViewUserDataCell" private struct Constraints { @@ -90,9 +78,9 @@ class PaymentTableViewCell: UITableViewCell { } -class PaymentTableViewAmountCell: UITableViewCell, UITextFieldDelegate { +class PaymentTableTextInputCell: UITableViewCell { - static let cellId = "PaymentTableViewAmountCell" + static let cellId = "PaymentTableTextInputCell" private struct Constraints { struct textField { @@ -103,10 +91,9 @@ class PaymentTableViewAmountCell: UITableViewCell, UITextFieldDelegate { lazy var textField: MaterialTextField = { let textField = MaterialTextField() - textField.keyboardType = .numberPad contentView.addSubview(textField) - + textField.snp.makeConstraints { make in make.top.equalToSuperview().offset(Constraints.textField.topInset) make.left.right.equalToSuperview().inset(Constraints.textField.horizontalInset) diff --git a/Nynja/Modules/Wallet Flows/Payment/View/PaymentViewController.swift b/Nynja/Modules/Wallet Flows/Payment/View/PaymentViewController.swift new file mode 100644 index 000000000..91eeedba7 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/Payment/View/PaymentViewController.swift @@ -0,0 +1,303 @@ +// +// PaymentViewController.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/20/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class PaymentViewController: BaseVC, PaymentViewProtocol, SetInjectable, UITextFieldDelegate, UITableViewDelegate, +UITableViewDataSource, TestableViewControllerProtocol { + + private var presenter: PaymentPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + private var model = PaymentViewControllerModel() + + private weak var textField: UITextField? + + //MARK: PaymentViewProtocol + func showActivityIndicator() { + showSpinner() + } + + func hideActivityIndicator() { + hideSpinner() + } + func setup(with model: PaymentViewControllerModel) { + self.model = model + + tableView.reloadData() + view.bringSubview(toFront: sendButton) + + showAllViews() + } + + func showAlert(_ text: String) { + AlertManager.sharedInstance.showAlertOk(message: text) + } + + func setupPassocdeInput() { + hideAllViews() + showPasscodeAlertAtOpening() + } + + // MARK: - Life Cycle + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + presenter.showed() + } + + + private func setupNavigationView() { + shouldShowSeparator = true + screenTitle = "wheel_item_transfer".localized.uppercased() + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: shouldShowSeparator, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.cancel.image)) + } + + private func showPassocdeAlert(with okAction: @escaping (String) -> ()) { + AlertManager.sharedInstance.showAlertOkCancel( + title: "enter_passcode".localized, + message: "", + textFieldConfig: { [weak self] textField in + textField.keyboardType = .numberPad + textField.delegate = self + textField.isSecureTextEntry = true + self?.textField = textField + }, + completionOk: { [weak self] action in + if let text = self?.textField?.text { + okAction(text) + } else { + self?.presenter.passcodeInputCanceled() + } + + }, + completionCancel: { [weak self] action in + self?.presenter.passcodeInputCanceled() + } + ) + } + + private func showPasscodeAlertAtOpening() { + showPassocdeAlert(with: presenter.setupWallet) + } + + private func showPasscodeAlertAtSending() { + showPassocdeAlert(with: presenter.sendCoins) + } + + private func hideAllViews() { + changesUIVisibility(to: true) + } + + private func showAllViews() { + changesUIVisibility(to: false) + } + + private func changesUIVisibility(to visible: Bool) { + tableView.isHidden = visible + sendButton.isHidden = visible + } + + // MARK: - UI Setup + + private func setupUI() { + setupNavigationView() + hideAllViews() + sendButton.isEnabled = false + } + + private lazy var sendButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("wallet_send".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(sendTapped), for: .touchUpInside) + + let horizontalInset = 16.0.adjustedByWidth + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(44.0.adjustedByWidth) + make.left.right.equalToSuperview().inset(horizontalInset) + make.bottom.equalTo(self.view.keyboardLayoutGuide.snp.top).offset(-28.0.adjustedByWidth) + } + + return button + }() + + private lazy var tableView: UITableView = { + let table = UITableView.default + table.delegate = self + table.dataSource = self + table.backgroundColor = UIColor.nynja.clear + table.separatorStyle = .singleLine + table.separatorColor = UIColor.nynja.backgroundGray + table.tableFooterView = UIView() + table.clipsToBounds = true + table.keyboardDismissMode = .onDrag; + + table.register(PaymentTableViewUserDataCell.self, forCellReuseIdentifier: PaymentTableViewUserDataCell.cellId) + table.register(PaymentTableTextInputCell.self, forCellReuseIdentifier: PaymentTableTextInputCell.cellId) + + view.addSubview(table) + table.snp.makeConstraints({ (make) in + make.top.equalTo(navigationView.snp.bottom) + make.bottom.left.right.bottom.equalToSuperview() + }) + + return table + }() + + //MARK: Actions + @objc private func sendTapped() { + view.endEditing(true) + showPasscodeAlertAtSending() + } +} + +// MARK: - Testable +extension PaymentViewController { + func setupTestingKeys() { + tableView.accessibilityIdentifier = "table_view" + sendButton.accessibilityIdentifier = "send_button" + } +} + +//MARK: SetInjectable +extension PaymentViewController { + struct Dependencies { + let presenter: PaymentPresenterProtocol + } + + func inject(dependencies: PaymentViewController.Dependencies) { + presenter = dependencies.presenter + } +} + +//MARK: UITextFieldDelegate +extension PaymentViewController { + func textField(_ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + return presenter.passcodeAlertTextSholdChange(text: textField.text, range: range, string: string) + } +} + +//MARK: UITableViewDelegate, UITableViewDataSource +extension PaymentViewController { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return model.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellModel = model[indexPath.row] + + var cellOptional: UITableViewCell? { + switch cellModel.type { + case .userData(let body): + let cell = tableView.dequeueReusableCell(withIdentifier: PaymentTableViewUserDataCell.cellId, + for: indexPath) as? PaymentTableViewUserDataCell + cell?.titleLabel.text = cellModel.title + cell?.bodyLabel.text = body + cell?.isUserInteractionEnabled = false + return cell + + case .amount(let changeTextHandler): + let cell = tableView.dequeueReusableCell(withIdentifier: PaymentTableTextInputCell.cellId, + for: indexPath) as? PaymentTableTextInputCell + + cell?.textField.placeholder = cellModel.title + cell?.textField.keyboardType = .numberPad + cell?.textField.textChanged = { [weak self] input in + guard let `self` = self else { return } + let info = self.presenter.amountChanged(input.text) + cell?.textField.info = info + self.sendButton.isEnabled = (info == nil) + } + + cell?.textField.shouldTextChanged = changeTextHandler + return cell + + case .notes: + + let cell = tableView.dequeueReusableCell( + withIdentifier: PaymentTableTextInputCell.cellId, + for: indexPath) as? PaymentTableTextInputCell + + cell?.textField.placeholder = cellModel.title + cell?.textField.shouldShowSeparator = false + cell?.textField.textChanged = { [weak self] input in + self?.presenter.notesChanged(input.text) + } + + return cell + } + } + + guard let cell = cellOptional else { + assertionFailure("Cell should be constuct") + return UITableViewCell(frame: CGRect.zero) + } + + cell.backgroundColor = UIColor.nynja.clear + cell.selectionStyle = UITableViewCellSelectionStyle.none + + return cell + } + + func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + let cellModel = model[indexPath.row] + + return cellModel.height + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let cellModel = model[indexPath.row] + + return cellModel.height + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + let cellModel = model[indexPath.row] + + var edgeInsets: UIEdgeInsets { + switch cellModel.type { + case .userData: return UIEdgeInsetsMake(0, + Constraints.tableView.separatorInset, + 0, + Constraints.tableView.separatorInset) + case .amount, .notes: return UIEdgeInsetsMake(0, cell.bounds.size.width, 0, 0) + } + } + + cell.separatorInset = edgeInsets + } +} + +extension PaymentViewController { + enum Constraints { + enum tableView { + static let separatorInset = CGFloat(16.0.adjustedByWidth) + } + + enum Cells { + static let userDataCellHeight = CGFloat(64.adjustedByWidth) + static let amountCellHeight = CGFloat(80.adjustedByWidth) + } + } +} diff --git a/Nynja/Modules/Payment/WireFrame/PaymentWireFrame.swift b/Nynja/Modules/Wallet Flows/Payment/WireFrame/PaymentWireFrame.swift similarity index 62% rename from Nynja/Modules/Payment/WireFrame/PaymentWireFrame.swift rename to Nynja/Modules/Wallet Flows/Payment/WireFrame/PaymentWireFrame.swift index beb448590..ce0a5dba9 100644 --- a/Nynja/Modules/Payment/WireFrame/PaymentWireFrame.swift +++ b/Nynja/Modules/Wallet Flows/Payment/WireFrame/PaymentWireFrame.swift @@ -12,7 +12,7 @@ final class PaymentWireFrame: PaymentWireFrameProtocol { weak var navigation: UINavigationController? - func presentPayment(navigation: UINavigationController, contact: Contact) { + func presentPayment(navigation: UINavigationController, contact: Contact, profile: Profile) { self.navigation = navigation let serviceFactory = ServiceFactory() @@ -29,11 +29,23 @@ final class PaymentWireFrame: PaymentWireFrameProtocol { let presenterDependencies = PaymentPresenter.Dependencies(view: view, interactor: interactor, wireFrame: self) presenter.inject(dependencies: presenterDependencies) - let interactorDependencies = PaymentInteractor.Dependencies(presenter: presenter, - recepientContact: contact, - walletService: serviceFactory.makeWalletService(), - messageSendingService: serviceFactory.makeMessageSendingService(), - messageFactory: serviceFactory.makeMessageFactory()) + let inputValidationService = serviceFactory.makeWalletOpeningTextInputValidationService() + let messageSendingService = serviceFactory.makeMessageSendingService() + let walletService = serviceFactory.makeWalletService() + let messageFactory = serviceFactory.makeMessageFactory() + let profileService = ProfileServices(profile: profile) + let contactServices = ContactServices(contact: contact) + + let interactorDependencies = PaymentInteractor.Dependencies( + presenter: presenter, + recipientContact: contact, + walletService: walletService, + messageSendingService: messageSendingService, + messageFactory: messageFactory, + profile: profile, + profileServices: profileService, + inputValidationService: inputValidationService, + contactServices: contactServices) interactor.inject(dependencies: interactorDependencies) // Presenting diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletInputParams.swift b/Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletInputParams.swift new file mode 100644 index 000000000..64e9b5336 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletInputParams.swift @@ -0,0 +1,13 @@ +// +// SeedBackupWalletInputParams.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct SeedBackupWalletInputParams { + let name: String + let passcode: String + let profile: Profile +} diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletOutputParams.swift b/Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletOutputParams.swift new file mode 100644 index 000000000..33bcb2fa7 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/Entities/SeedBackupWalletOutputParams.swift @@ -0,0 +1,14 @@ +// +// SeedBackupWalletOutputParams.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct SeedBackupWalletOutputParams { + let name: String + let passcode: String + let profile: Profile + let seeds: [String] +} diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/Interactor/SeedBackupWalletInteractor.swift b/Nynja/Modules/Wallet Flows/SeedBackup/Interactor/SeedBackupWalletInteractor.swift new file mode 100644 index 000000000..7a0e8dcba --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/Interactor/SeedBackupWalletInteractor.swift @@ -0,0 +1,69 @@ +// +// SeedBackupWalletInteractor.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class SeedBackupWalletInteractor: SeedBackupWalletInteractorInputProtocol, SetInjectable { + private weak var presenter: SeedBackupWalletInteractorOutputProtocol! + private var walletService: WalletServiceProtocol! + private var walletName: String! + private var passcode: String! + private var seeds: [String]? + private var profile: Profile! +} + +//MARK: SeedBackupWalletInteractorInputProtocol +extension SeedBackupWalletInteractor { + + func getSeeds(completion: (([String]) -> ())) { + walletService.makeMnemonics() { [weak self] result in + guard let `self` = self else { + return + } + + var seeds: [String] { + switch result { + case .success(let seeds): + return seeds + case .failure: + return [] + } + } + + self.seeds = seeds + completion(seeds) + } + } + + func getOutParamsSeeds() -> SeedBackupWalletOutputParams? { + return seeds.map { SeedBackupWalletOutputParams( + name: walletName, + passcode: passcode, + profile: profile, + seeds: $0) + } + } +} + +extension SeedBackupWalletInteractor { + struct Dependencies { + let presenter: SeedBackupWalletInteractorOutputProtocol + let profile: Profile + let walletService: WalletServiceProtocol + let walletName: String + let passcode: String + } + + func inject(dependencies: SeedBackupWalletInteractor.Dependencies) { + presenter = dependencies.presenter + walletService = dependencies.walletService + walletName = dependencies.walletName + passcode = dependencies.passcode + profile = dependencies.profile + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/Presenter/SeedBackupWalletPresenter.swift b/Nynja/Modules/Wallet Flows/SeedBackup/Presenter/SeedBackupWalletPresenter.swift new file mode 100644 index 000000000..6766c2523 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/Presenter/SeedBackupWalletPresenter.swift @@ -0,0 +1,50 @@ +// +// SeedBackupWalletPresenter.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class SeedBackupWalletPresenter: BasePresenter, SeedBackupWalletPresenterProtocol, SeedBackupWalletInteractorOutputProtocol { + + private weak var view: SeedBackupWalletViewProtocol! + private var interactor: SeedBackupWalletInteractorInputProtocol! + private var wireFrame: SeedBackupWalletWireFrameProtocol! + + + // MARK: - SeedBackupWalletPresenterProtocol + func showed() { + interactor.getSeeds { [weak self] (seeds) in + dispatchAsyncMain { + self?.view.setup(with: seeds) + } + } + } + + func showVerificationScreen() { + guard let params = interactor.getOutParamsSeeds() else { return } + wireFrame.showVerificationScreen(with: params) + } + + //MARK: NavigationProtocol + func back() { + wireFrame.close() + } +} + +extension SeedBackupWalletPresenter: SetInjectable { + struct Dependencies { + let view: SeedBackupWalletViewProtocol + let interactor: SeedBackupWalletInteractorInputProtocol + let wireFrame: SeedBackupWalletWireFrameProtocol + } + + func inject(dependencies: SeedBackupWalletPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/SeedBackupWalletProtocols.swift b/Nynja/Modules/Wallet Flows/SeedBackup/SeedBackupWalletProtocols.swift new file mode 100644 index 000000000..f33a699c6 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/SeedBackupWalletProtocols.swift @@ -0,0 +1,32 @@ +// +// SeedBackupWalletProtocols.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol SeedBackupWalletWireFrameProtocol: class { + + func presentSeedBackupWallet(params: SeedBackupWalletInputParams, navigation: UINavigationController) + func close() + func showVerificationScreen(with params: SeedBackupWalletOutputParams) +} + +protocol SeedBackupWalletViewProtocol: class { + func setup(with seeds: [String]) +} + +protocol SeedBackupWalletPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + + func showed() + func showVerificationScreen() +} + +protocol SeedBackupWalletInteractorOutputProtocol: class { +} + +protocol SeedBackupWalletInteractorInputProtocol: class { + func getSeeds(completion: (([String]) -> ())) + func getOutParamsSeeds() -> SeedBackupWalletOutputParams? +} diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletCollectionViewCell.swift b/Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletCollectionViewCell.swift new file mode 100644 index 000000000..d2ddf75c9 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletCollectionViewCell.swift @@ -0,0 +1,152 @@ +// +// SeedBackupWalletCollectionViewCell.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/23/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +final class SeedBackupWalletCollectionViewCell: UICollectionViewCell { + + static var cellId = "SeedBackupWalletCollectionViewCell" + static var accessibilityPrefix = "SeedBackupWalletCollectionViewCell" + + private lazy var infoTextView: UITextView = { + let textView = UITextView() + + textView.isUserInteractionEnabled = true + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false + + textView.backgroundColor = UIColor.clear + textView.textContainerInset = UIEdgeInsets.zero + textView.textContainer.lineFragmentPadding = Consts.infoTextView.lineFragmentPadding + textView.font = Consts.font + textView.textColor = UIColor.nynja.manatee + + contentView.addSubview(textView) + + textView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.left.right.equalToSuperview().inset(Consts.infoTextView.horizontalInset) + make.height.equalTo(Consts.infoTextView.height) + } + return textView + }() + + private lazy var seedTextView: UITextView = { + let textView = UITextView() + + textView.isUserInteractionEnabled = true + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false + + textView.backgroundColor = UIColor.nynja.black.withAlphaComponent(0.1) + textView.textContainer.lineFragmentPadding = Consts.seedTextView.lineFragmentPadding + textView.font = Consts.font + textView.textColor = UIColor.nynja.manatee + textView.textContainerInset = Consts.seedTextView.textContainerInset + + contentView.addSubview(textView) + + textView.snp.makeConstraints { make in + make.top.equalTo(infoTextView.snp.bottom).inset(-Consts.seedTextView.topInset) + make.left.right.equalToSuperview() + make.height.equalTo(Consts.seedTextView.height) + } + return textView + }() + + func setInfoText(_ text: String) { + guard let attributedText = attributedInfoText(text) else { + return + } + infoTextView.attributedText = attributedText + infoTextView.setNeedsLayout() + infoTextView.layoutIfNeeded() + + let size = infoTextView.sizeThatFits( + CGSize(width: infoTextView.frame.size.width, + height: CGFloat.greatestFiniteMagnitude)) + + infoTextView.snp.updateConstraints { (make) in + make.height.equalTo(size.height) + } + } + + func setSeedTextWith(prefix: String, number: Int, seed: String) { + seedTextView.attributedText = makeAttributedSeedText(prefix: prefix, number: number, seed: seed) + } + + private func attributedInfoText(_ text: String) -> NSAttributedString? { + guard let font = Consts.font else { return nil } + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.paragraphSpacing = font.lineHeight / 2 + + return NSMutableAttributedString( + string: text, + attributes: [NSAttributedStringKey.font: font, + NSAttributedStringKey.foregroundColor: UIColor.nynja.manatee, + NSAttributedStringKey.paragraphStyle: paragraphStyle]) + } +} + +private extension SeedBackupWalletCollectionViewCell { + + func makeAttributedSeedText(prefix: String, number: Int, seed: String) -> NSMutableAttributedString? { + let fullPrefix = makeFullPrefix(with: prefix, number: number) + + guard + let attributedTitle = makeAttributedSeed(with: fullPrefix), + let attributedSeed = makeAttributedSeed(with: seed) else { + return nil + } + + let finalString = NSMutableAttributedString() + finalString.append(attributedTitle) + finalString.append(attributedSeed) + + return finalString + } + + func makeFullPrefix(with prefix: String, number: Int) -> String { + return prefix + " " + "\(number)" + ":" + " " + } + + func makeAttributedPrefix(with text: String) -> NSAttributedString? { + guard let font = Consts.font else { return nil } + return NSMutableAttributedString( + string: text, + attributes: [NSAttributedStringKey.font: font, + NSAttributedStringKey.foregroundColor: UIColor.nynja.manatee]) + } + + func makeAttributedSeed(with text: String) -> NSAttributedString? { + guard let font = Consts.font else { return nil } + return NSMutableAttributedString( + string: text, + attributes: [NSAttributedStringKey.font: font, + NSAttributedStringKey.foregroundColor: UIColor.white]) + } +} + +private extension SeedBackupWalletCollectionViewCell { + enum Consts { + static let font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 18) + + enum infoTextView { + static let height = CGFloat(225) + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let lineFragmentPadding = CGFloat(0) + } + + enum seedTextView { + static let lineFragmentPadding = CGFloat(0) + static let height = CGFloat(32.adjustedByWidth) + static let topInset = CGFloat(10.adjustedByWidth) + static let textContainerInset: UIEdgeInsets = UIEdgeInsetsMake(4, 16, 0, 0) + } + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletViewController.swift b/Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletViewController.swift new file mode 100644 index 000000000..75a2a4aa5 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/View/SeedBackupWalletViewController.swift @@ -0,0 +1,311 @@ +// +// SeedBackupWalletViewController.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class SeedBackupWalletViewController: BaseVC, SeedBackupWalletViewProtocol, TestableViewControllerProtocol, +SetInjectable, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource { + + private var presenter: SeedBackupWalletPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + private enum PagingDirection { + case forward + case reverse + } + + private var seeds = [String]() + + // MARK: - Views + + private lazy var collectionView: UICollectionView = { + + let layout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = Consts.collectionView.minimumLineSpacing + return layout + }() + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.register( + SeedBackupWalletCollectionViewCell.self, + forCellWithReuseIdentifier: SeedBackupWalletCollectionViewCell.cellId) + + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = UIColor.clear + collectionView.dataSource = self + collectionView.delegate = self + collectionView.isUserInteractionEnabled = false + + view.addSubview(collectionView) + collectionView.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom).inset(-Consts.collectionView.topInset) + make.right.left.equalToSuperview() + make.bottom.equalTo(nextButton.snp.top).offset(-Consts.collectionView.bottomOffset) + } + + return collectionView + }() + + private lazy var previousButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("previous".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(previuosSeedButtonAction), for: .touchUpInside) + + button.defaultColor = UIColor.clear + button.highlightedColor = UIColor.clear + button.isHighlighted = false + button.layer.borderWidth = Consts.previousButton.borderWidth + button.layer.borderColor = UIColor.nynja.mainRed.cgColor + + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .disabled) + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(Consts.previousButton.height) + make.width.equalTo(Consts.previousButton.width) + make.left.equalToSuperview().inset(Consts.previousButton.horizontalInset) + make.bottom.equalToSuperview().offset(-Consts.previousButton.bottomOffset) + } + + return button + }() + + private lazy var nextButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("next".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(nextSeedButtonAction), for: .touchUpInside) + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(Consts.nextButton.height) + make.width.equalTo(Consts.nextButton.width) + make.right.equalToSuperview().inset(Consts.nextButton.horizontalInset) + make.bottom.equalToSuperview().offset(-Consts.nextButton.bottomOffset) + } + + return button + }() + + private var firstVisibleCellIndexPath: IndexPath? { + return collectionView.visibleCells.first.flatMap { collectionView.indexPath(for: $0) } + } + + private var nextIndexPath: IndexPath? { + guard let indexPath = firstVisibleCellIndexPath else { + return nil + } + + let newIndex = indexPath.row + 1 + + guard newIndex < seeds.count else { + return nil + } + + return IndexPath(item: newIndex, section: 0) + } + + private var previousIndexPath: IndexPath? { + guard let indexPath = firstVisibleCellIndexPath else { + return nil + } + + let newIndex = indexPath.row - 1 + + guard newIndex >= 0 else { + return nil + } + + return IndexPath(item: newIndex, section: 0) + } + + private func animateScroll(_ indexPath: IndexPath) { + collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) + } + + private var isLastSeedShown: Bool { + return firstVisibleCellIndexPath?.row == seeds.count - 1 + } + + private var isLastSeedWillShow: Bool { + guard let row = firstVisibleCellIndexPath?.row else { return false } + return row >= seeds.count - 2 + } + + private func setNextButtonTitle(_ direction: PagingDirection) { + + var title: String { + switch (direction, isLastSeedWillShow) { + case (.reverse, _): + return "next" + case (.forward, true): + return "wallet_start_verification" + case (.forward, false): + return "next" + } + } + + nextButton.setTitle(title.localized.uppercased(), for: .normal) + } + + //MARK: Actions + @objc private func nextSeedButtonAction() { + setNextButtonTitle(.forward) + + if isLastSeedShown { + showSeedVerification() + } else { + showNextSeed() + } + } + + @objc private func previuosSeedButtonAction() { + setNextButtonTitle(.reverse) + + guard let indexPath = previousIndexPath else { + return + } + + animateScroll(indexPath) + } + + private func showNextSeed() { + + guard let indexPath = nextIndexPath else { + return + } + animateScroll(indexPath) + } + + private func showSeedVerification() { + presenter.showVerificationScreen() + } + + // MARK: - Life Cycle + + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + presenter.showed() + } + + + // MARK: - UI Setup + + private func setupUI() { + + shouldShowSeparator = true + screenTitle = "wallet_seed_backup".localized.uppercased() + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: shouldShowSeparator, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + + nextButton.isHidden = false + previousButton.isHidden = false + collectionView.isHidden = false + } +} + +//MARK: SeedBackupWalletViewProtocol +extension SeedBackupWalletViewController { + func setup(with seeds: [String]) { + self.seeds = seeds + collectionView.reloadData() + } +} + +// MARK: - Testable +extension SeedBackupWalletViewController { + func setupTestingKeys() { + nextButton.accessibilityIdentifier = "next_button" + previousButton.accessibilityIdentifier = "previous_button" + collectionView.accessibilityIdentifier = "collection_view" + } +} + +extension SeedBackupWalletViewController { + struct Dependencies { + let presenter: SeedBackupWalletPresenterProtocol + } + + func inject(dependencies: SeedBackupWalletViewController.Dependencies) { + presenter = dependencies.presenter + } +} + +//MARK: UICollectionViewDelegate, UICollectionViewDataSource +extension SeedBackupWalletViewController { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return seeds.count + } + + func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cellSeed = seeds[indexPath.row] + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: SeedBackupWalletCollectionViewCell.cellId, + for: indexPath) + if let seedBackupCell = cell as? SeedBackupWalletCollectionViewCell { + seedBackupCell.setInfoText("wallet_seed_description".localized) + + let seedPrefix = "wallet_word".localized + seedBackupCell.setSeedTextWith(prefix: seedPrefix, number: indexPath.row + 1, seed: cellSeed) + } + return cell + } +} + +//MARK: UICollectionViewDelegateFlowLayout +extension SeedBackupWalletViewController { + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + return collectionView.frame.size + } +} + +private extension SeedBackupWalletViewController { + enum Consts { + enum collectionView { + static let topInset = CGFloat(21.adjustedByWidth) + static let bottomOffset = CGFloat(28.adjustedByWidth) + static let minimumLineSpacing = CGFloat(0) + } + + enum previousButton { + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let borderWidth = CGFloat(2.0) + static let height = CGFloat(44.0.adjustedByWidth) + static let width = CGFloat(187.0.adjustedByWidth) + static let bottomOffset = CGFloat(28.0.adjustedByWidth) + } + + enum nextButton { + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let borderWidth = CGFloat(2.0) + static let height = CGFloat(44.0.adjustedByWidth) + static let width = CGFloat(187.0.adjustedByWidth) + static let bottomOffset = CGFloat(28.0.adjustedByWidth) + } + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedBackup/WireFrame/SeedBackupWalletWireFrame.swift b/Nynja/Modules/Wallet Flows/SeedBackup/WireFrame/SeedBackupWalletWireFrame.swift new file mode 100644 index 000000000..4ee96a649 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedBackup/WireFrame/SeedBackupWalletWireFrame.swift @@ -0,0 +1,61 @@ +// +// SeedBackupWalletWireFrame.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class SeedBackupWalletWireFrame: SeedBackupWalletWireFrameProtocol { + + private weak var navigationController: UINavigationController? + private let serviceFactory = ServiceFactory() + + func presentSeedBackupWallet(params: SeedBackupWalletInputParams, navigation: UINavigationController) { + navigationController = navigation + + let view = SeedBackupWalletViewController() + let presenter = SeedBackupWalletPresenter() + let interactor = SeedBackupWalletInteractor() + + // Module components + let viewDependencies = SeedBackupWalletViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + + let presenterDependencies = SeedBackupWalletPresenter.Dependencies( + view: view, + interactor: interactor, + wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + + let interactorDependencies = SeedBackupWalletInteractor.Dependencies( + presenter: presenter, + profile: params.profile, + walletService: serviceFactory.makeWalletService(), + walletName: params.name, + passcode: params.passcode) + interactor.inject(dependencies: interactorDependencies) + + + navigationController?.pushViewController(view, animated: true) + } + + func close() { + navigationController?.popViewController(animated: true) + } + + func showVerificationScreen(with params: SeedBackupWalletOutputParams) { + guard let navigationController = navigationController else { return } + SeedVerificationWalletWireFrame().presentSeedVerificationWallet( + navigation: navigationController, + params: SeedVerificationWalletInput( + name: params.name, + passcode: params.passcode, + profile: params.profile, + seeds: params.seeds)) + } +} + + diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/Entities/SeedVerificationWalletInput.swift b/Nynja/Modules/Wallet Flows/SeedVerification/Entities/SeedVerificationWalletInput.swift new file mode 100644 index 000000000..9c9cf4187 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedVerification/Entities/SeedVerificationWalletInput.swift @@ -0,0 +1,14 @@ +// +// SeedVerificationWalletInput.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct SeedVerificationWalletInput { + let name: String + let passcode: String + let profile: Profile + let seeds: [String] +} diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift b/Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift new file mode 100644 index 000000000..b22d6216e --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift @@ -0,0 +1,59 @@ +// +// SeedVerificationWalletInteractor.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class SeedVerificationWalletInteractor: SeedVerificationWalletInteractorInputProtocol, SetInjectable { + + private weak var presenter: SeedVerificationWalletInteractorOutputProtocol! + private var walletService: WalletServiceProtocol! + private var profile: Profile! + private var name: String! + private var passcode: String! + private var seeds: [String]! +} + +//MARK: SeedVerificationWalletInteractorInputProtocol +extension SeedVerificationWalletInteractor { + + func getSeeds(completion: (([String]) -> ())) { + completion(seeds) + } + + func isValid(seed: String, at index: Int) -> Bool { + guard index < seeds.count else { return false } + return seed == seeds[index] + } + + func createWallet(completion: @escaping (Error?) -> ()) { + walletService.makeNynjaWallet(seeds: seeds, name: name, passcode: passcode) { (result) in + completion(result.error) + } + } +} + +extension SeedVerificationWalletInteractor { + struct Dependencies { + let presenter: SeedVerificationWalletInteractorOutputProtocol + let profile: Profile + let walletService: WalletServiceProtocol + let name: String + let passcode: String + let seeds: [String] + } + + func inject(dependencies: SeedVerificationWalletInteractor.Dependencies) { + presenter = dependencies.presenter + walletService = dependencies.walletService + profile = dependencies.profile + name = dependencies.name + passcode = dependencies.passcode + seeds = dependencies.seeds + + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/Presenter/SeedVerificationWalletPresenter.swift b/Nynja/Modules/Wallet Flows/SeedVerification/Presenter/SeedVerificationWalletPresenter.swift new file mode 100644 index 000000000..786f7472b --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedVerification/Presenter/SeedVerificationWalletPresenter.swift @@ -0,0 +1,65 @@ +// +// SeedVerificationWalletPresenter.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class SeedVerificationWalletPresenter: BasePresenter, SeedVerificationWalletPresenterProtocol, SeedVerificationWalletInteractorOutputProtocol { + + private weak var view: SeedVerificationWalletViewProtocol! + private var interactor: SeedVerificationWalletInteractorInputProtocol! + private var wireFrame: SeedVerificationWalletWireFrameProtocol! + + + // MARK: - SeedVerificationWalletPresenterProtocol + func validateSeed(with text: String, at index: Int) { + let isValid = interactor.isValid(seed: text, at: index) + let input: InputInfo? = isValid ? nil : InputInfo(text: "", kind: .warning) + view.updateSeedInputView(with: input) + } + func showed() { + interactor.getSeeds { [weak self] (seeds) in + dispatchAsyncMain { + self?.view.setup(with: seeds) + } + } + } + + func showWallets() { + wireFrame.showWallets() + } + + func triggerWalletCreation() { + view.showWalletCreationInProgress() + interactor.createWallet { [weak self] (error) in + if let error = error { + self?.view.showWalletCreateErrorView(with: error) + } else { + self?.view.showWalletCreateSuccessView() + } + } + } + + //MARK: NavigationProtocol + func back() { + wireFrame.close() + } +} + +extension SeedVerificationWalletPresenter: SetInjectable { + struct Dependencies { + let view: SeedVerificationWalletViewProtocol + let interactor: SeedVerificationWalletInteractorInputProtocol + let wireFrame: SeedVerificationWalletWireFrameProtocol + } + + func inject(dependencies: SeedVerificationWalletPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/SeedVerificationWalletProtocols.swift b/Nynja/Modules/Wallet Flows/SeedVerification/SeedVerificationWalletProtocols.swift new file mode 100644 index 000000000..86fd8e12b --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedVerification/SeedVerificationWalletProtocols.swift @@ -0,0 +1,41 @@ +// +// SeedVerificationWalletProtocols.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol SeedVerificationWalletWireFrameProtocol: class { + + func presentSeedVerificationWallet(navigation: UINavigationController, params: SeedVerificationWalletInput) + func close() + func showWallets() +} + +protocol SeedVerificationWalletViewProtocol: class { + func showWalletCreationInProgress() + func showWalletCreateSuccessView() + func showWalletCreateErrorView(with error: Error) + func setup(with seeds: [String]) + func updateSeedInputView(with info: InputInfo?) +} + +protocol SeedVerificationWalletPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + + func showed() + func showWallets() + func triggerWalletCreation() + func validateSeed(with text: String, at index: Int) +} + +protocol SeedVerificationWalletInteractorOutputProtocol: class { +} + +protocol SeedVerificationWalletInteractorInputProtocol: class { + func getSeeds(completion: (([String]) -> ())) + func createWallet(completion: @escaping (Error?) -> ()) + func isValid(seed: String, at index: Int) -> Bool +} diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletCollectionViewCell.swift b/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletCollectionViewCell.swift new file mode 100644 index 000000000..3ca5b0957 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletCollectionViewCell.swift @@ -0,0 +1,115 @@ +// +// SeedVerificationWalletCollectionViewCell.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/23/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +class SeedVerificationWalletCollectionViewCell: UICollectionViewCell { + + static var cellId = "SeedVerificationWalletCollectionViewCell" + static var accessibilityPrefix = "SeedVerificationWalletCollectionViewCell" + + private lazy var infoTextView: UITextView = { + let textView = UITextView() + + textView.isUserInteractionEnabled = true + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false + + textView.backgroundColor = UIColor.clear + textView.textContainerInset = UIEdgeInsets.zero + textView.textContainer.lineFragmentPadding = Consts.infoTextView.lineFragmentPadding + textView.font = Consts.font + textView.textColor = UIColor.nynja.manatee + + contentView.addSubview(textView) + + textView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.left.right.equalToSuperview().inset(Consts.infoTextView.horizontalInset) + make.height.equalTo(Consts.infoTextView.height) + } + return textView + }() + + lazy var countTextView: UITextView = { + let textView = UITextView() + + textView.isUserInteractionEnabled = true + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false + textView.textAlignment = .center + textView.textContainer.lineFragmentPadding = Consts.countTextView.lineFragmentPadding + textView.font = Consts.font + textView.textColor = UIColor.nynja.manatee + textView.textContainerInset = Consts.countTextView.textContainerInset + textView.backgroundColor = UIColor.clear + + contentView.addSubview(textView) + + textView.snp.makeConstraints { make in + make.top.equalTo(infoTextView.snp.bottom).inset(-Consts.countTextView.topInset) + make.left.right.equalToSuperview() + make.height.equalTo(Consts.countTextView.height) + } + return textView + }() + + lazy var wordTextField: MaterialTextField = { + let textField = MaterialTextField() + textField.backgroundColor = UIColor.clear + textField.autocapitalizationType = UITextAutocapitalizationType.none + + contentView.addSubview(textField) + + textField.snp.makeConstraints { make in + make.top.equalTo(countTextView.snp.bottom) + make.height.equalTo(Consts.wordTextField.height) + make.left.right.equalToSuperview().inset(Consts.wordTextField.horizontalInset) + } + + return textField + }() + + func setInfoText(_ text: String) { + + infoTextView.text = text + infoTextView.setNeedsLayout() + infoTextView.layoutIfNeeded() + + let size = infoTextView.sizeThatFits( + CGSize(width: infoTextView.frame.size.width, + height: CGFloat.greatestFiniteMagnitude)) + + infoTextView.snp.updateConstraints { (make) in + make.height.equalTo(size.height) + } + } +} + +private extension SeedVerificationWalletCollectionViewCell { + enum Consts { + static let font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 18) + enum infoTextView { + static let height = CGFloat(61) + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let lineFragmentPadding = CGFloat(0) + } + + enum countTextView { + static let lineFragmentPadding = CGFloat(0) + static let height = CGFloat(32.adjustedByWidth) + static let topInset = CGFloat(7.adjustedByWidth) + static let textContainerInset: UIEdgeInsets = UIEdgeInsetsMake(4, 16, 0, 0) + } + + enum wordTextField { + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let height = CGFloat(65.adjustedByWidth) + } + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift b/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift new file mode 100644 index 000000000..26a760216 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedVerification/View/SeedVerificationWalletViewController.swift @@ -0,0 +1,281 @@ +// +// SeedVerificationWalletViewController.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class SeedVerificationWalletViewController: BaseVC, SeedVerificationWalletViewProtocol, +TestableViewControllerProtocol, UICollectionViewDelegateFlowLayout { + + private var presenter: SeedVerificationWalletPresenterProtocol! { + didSet { + _presenter = presenter + } + } + private var alertManager: AlertManager! + + private var currentSeedCellIndexPath: IndexPath = IndexPath(row: 0, section: 0) + + var currentCell: SeedVerificationWalletCollectionViewCell? { + return collectionView.cellForItem(at: currentSeedCellIndexPath) as? SeedVerificationWalletCollectionViewCell + } + + var seeds = [String]() + + // MARK: - Views + + private lazy var collectionView: UICollectionView = { + + let layout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = Consts.collectionView.minimumLineSpacing + return layout + }() + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.register( + SeedVerificationWalletCollectionViewCell.self, + forCellWithReuseIdentifier: SeedVerificationWalletCollectionViewCell.cellId) + + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = UIColor.clear + collectionView.dataSource = self + collectionView.delegate = self + collectionView.isScrollEnabled = false + + view.addSubview(collectionView) + collectionView.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom).inset(-Consts.collectionView.topInset) + make.right.left.equalToSuperview() + make.bottom.equalToSuperview() + } + + return collectionView + }() + + private lazy var nextButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("next".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(nextSeedButtonAction), for: .touchUpInside) + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(Consts.nextButton.height) + make.left.right.equalToSuperview().inset(Consts.nextButton.horizontalInset) + make.bottom.equalTo(view.keyboardLayoutGuide.snp.top).offset(-Consts.nextButton.bottomOffset) + } + + return button + }() + + private var nextIndexPath: IndexPath? { + let newIndex = currentSeedCellIndexPath.row + 1 + + guard newIndex < seeds.count else { + return nil + } + + return IndexPath(item: newIndex, section: 0) + } + + private func animateScroll(_ indexPath: IndexPath) { + collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) + } + + private var isLastSeedShown: Bool { + return currentSeedCellIndexPath.row == seeds.count - 1 + } + + private var isLastSeedWillShow: Bool { + return currentSeedCellIndexPath.row >= seeds.count - 2 + } + + private func setNextButtonTitle() { + var title: String { + if isLastSeedWillShow { + return "confirm" + } + return "next" + } + + nextButton.setTitle(title.localized.uppercased(), for: .normal) + } + + //MARK: Actions + @objc private func nextSeedButtonAction() { + setNextButtonTitle() + + if isLastSeedShown { + confirmSeedBackup() + } else { + showNextPage() + } + + } + + private func showNextPage() { + + guard let indexPath = nextIndexPath else { + return + } + currentSeedCellIndexPath = indexPath + animateScroll(indexPath) + } + + private func showWallets() { + presenter.showWallets() + } + + private func confirmSeedBackup() { + presenter.triggerWalletCreation() + } + + private func setCurrentCellFrirstResponder() { + if let cell = currentCell { + cell.wordTextField.setTextFieldFirstResponder() + } + } + + // MARK: - Life Cycle + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + presenter.showed() + } + + + // MARK: - UI Setup + + private func setupUI() { + + shouldShowSeparator = true + screenTitle = "wallet_seed_verification".localized.uppercased() + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: shouldShowSeparator, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + + collectionView.isHidden = false + view.bringSubview(toFront: nextButton) + nextButton.isEnabled = false + } +} + +// MARK: - Testable +extension SeedVerificationWalletViewController { + + func setupTestingKeys() { + collectionView.accessibilityIdentifier = "collection_view" + nextButton.accessibilityIdentifier = "next_button" + } +} + +extension SeedVerificationWalletViewController: SetInjectable { + struct Dependencies { + let presenter: SeedVerificationWalletPresenterProtocol + let alertManager: AlertManager + } + + func inject(dependencies: SeedVerificationWalletViewController.Dependencies) { + presenter = dependencies.presenter + alertManager = dependencies.alertManager + } +} + +extension SeedVerificationWalletViewController: UICollectionViewDelegate, UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return seeds.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: SeedVerificationWalletCollectionViewCell.cellId, + for: indexPath) + if let seedVerificationCell = cell as? SeedVerificationWalletCollectionViewCell { + seedVerificationCell.setInfoText("wallet_seed_verification_description".localized) + seedVerificationCell.countTextView.text = "\(indexPath.row + 1)" + "/12" + seedVerificationCell.wordTextField.text = "" + seedVerificationCell.wordTextField.placeholder = "wallet_enter_word".localized + seedVerificationCell.wordTextField.textChanged = { [weak self] input in + self?.presenter.validateSeed(with: input.text, at: indexPath.row) + } + } + return cell + } +} + +//MARK: UICollectionViewDelegateFlowLayout +extension SeedVerificationWalletViewController { + func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + return collectionView.frame.size + } + + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + setCurrentCellFrirstResponder() + } +} + +private extension SeedVerificationWalletViewController { + enum Consts { + enum collectionView { + static let topInset = CGFloat(21.adjustedByWidth) + static let minimumLineSpacing = CGFloat(0) + } + + enum nextButton { + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let height = CGFloat(44.0.adjustedByWidth) + static let bottomOffset = CGFloat(28.0.adjustedByWidth) + } + } +} + +//MARK: SeedVerificationWalletViewProtocol +extension SeedVerificationWalletViewController { + + func updateSeedInputView(with info: InputInfo?) { + guard let cell = currentCell else { + return + } + cell.wordTextField.info = info + nextButton.isEnabled = (info == nil) + } + + func setup(with seeds: [String]) { + self.seeds = seeds + collectionView.reloadData() + } + + func showWalletCreationInProgress() { + showSpinner() + } + + func showWalletCreateSuccessView() { + hideSpinner() + alertManager.showAlertOk( + title: "wallet_verification_success".localized, + message: "wallet_verification_success_description".localized) { [weak self] in + self?.showWallets() + } + } + + func showWalletCreateErrorView(with error: Error) { + hideSpinner() + alertManager.showAlertOk(title: "Error", message: error.localizedDescription) { [weak self] in + self?.showWallets() + } + } +} diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/WireFrame/SeedVerificationWalletWireFrame.swift b/Nynja/Modules/Wallet Flows/SeedVerification/WireFrame/SeedVerificationWalletWireFrame.swift new file mode 100644 index 000000000..c5d4bb09f --- /dev/null +++ b/Nynja/Modules/Wallet Flows/SeedVerification/WireFrame/SeedVerificationWalletWireFrame.swift @@ -0,0 +1,59 @@ +// +// SeedVerificationWalletWireFrame.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class SeedVerificationWalletWireFrame: SeedVerificationWalletWireFrameProtocol { + + private weak var navigationController: UINavigationController? + private let serviceFactory = ServiceFactory() + + func presentSeedVerificationWallet(navigation: UINavigationController, params: SeedVerificationWalletInput) { + navigationController = navigation + + let view = SeedVerificationWalletViewController() + let presenter = SeedVerificationWalletPresenter() + let interactor = SeedVerificationWalletInteractor() + + let alertManager = AlertManager.sharedInstance + + // Module components + let viewDependencies = SeedVerificationWalletViewController.Dependencies( + presenter: presenter, + alertManager: alertManager) + view.inject(dependencies: viewDependencies) + + let presenterDependencies = SeedVerificationWalletPresenter.Dependencies( + view: view, + interactor: interactor, + wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + + let interactorDependencies = SeedVerificationWalletInteractor.Dependencies( + presenter: presenter, + profile: params.profile, + walletService: serviceFactory.makeWalletService(), + name: params.name, + passcode: params.passcode, + seeds: params.seeds) + interactor.inject(dependencies: interactorDependencies) + + + navigationController?.pushViewController(view, animated: true) + } + + func close() { + navigationController?.popViewController(animated: true) + } + + func showWallets() { + navigationController?.popToRootViewController(animated: true) + } +} + + diff --git a/Nynja/Modules/Wallet Flows/TransferDetails/Interactor/TransferDetailsInteractor.swift b/Nynja/Modules/Wallet Flows/TransferDetails/Interactor/TransferDetailsInteractor.swift new file mode 100644 index 000000000..d8c09e9be --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferDetails/Interactor/TransferDetailsInteractor.swift @@ -0,0 +1,26 @@ +// +// TransferDetailsInteractor.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class TransferDetailsInteractor: TransferDetailsInteractorInputProtocol { + + private weak var presenter: TransferDetailsInteractorOutputProtocol! + + //MARK: TransferDetailsInteractorInputProtocol +} + +extension TransferDetailsInteractor: SetInjectable { + struct Dependencies { + let presenter: TransferDetailsInteractorOutputProtocol + } + + func inject(dependencies: TransferDetailsInteractor.Dependencies) { + presenter = dependencies.presenter + } +} diff --git a/Nynja/Modules/Wallet Flows/TransferDetails/Presenter/TransferDetailsPresenter.swift b/Nynja/Modules/Wallet Flows/TransferDetails/Presenter/TransferDetailsPresenter.swift new file mode 100644 index 000000000..01f3df4db --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferDetails/Presenter/TransferDetailsPresenter.swift @@ -0,0 +1,41 @@ +// +// TransferDetailsPresenter.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class TransferDetailsPresenter: BasePresenter, TransferDetailsPresenterProtocol, +TransferDetailsInteractorOutputProtocol { + + private weak var view: TransferDetailsViewProtocol! + private var interactor: TransferDetailsInteractorInputProtocol! + private var wireFrame: TransferDetailsWireFrameProtocol! + + + // MARK: - TransferDetailsPresenterProtocol + func showed() { + } + + //MARK: NavigationProtocol + func back() { + wireFrame.close() + } +} + +extension TransferDetailsPresenter: SetInjectable { + struct Dependencies { + let view: TransferDetailsViewProtocol + let interactor: TransferDetailsInteractorInputProtocol + let wireFrame: TransferDetailsWireFrameProtocol + } + + func inject(dependencies: TransferDetailsPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } +} diff --git a/Nynja/Modules/Wallet Flows/TransferDetails/TransferDetailsProtocols.swift b/Nynja/Modules/Wallet Flows/TransferDetails/TransferDetailsProtocols.swift new file mode 100644 index 000000000..ab5405004 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferDetails/TransferDetailsProtocols.swift @@ -0,0 +1,27 @@ +// +// TransferDetailsProtocols.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol TransferDetailsWireFrameProtocol: class { + func presentTransferDetails(navigation: UINavigationController) + func close() +} + +protocol TransferDetailsViewProtocol: class { +} + +protocol TransferDetailsPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + func showed() +} + +protocol TransferDetailsInteractorOutputProtocol: class { +} + +protocol TransferDetailsInteractorInputProtocol: class { +} diff --git a/Nynja/Modules/Wallet Flows/TransferDetails/View/TransferDetailsViewController.swift b/Nynja/Modules/Wallet Flows/TransferDetails/View/TransferDetailsViewController.swift new file mode 100644 index 000000000..6e59f871f --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferDetails/View/TransferDetailsViewController.swift @@ -0,0 +1,446 @@ +// +// TransferDetailsViewController.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class TransferDetailsViewController: BaseVC, TransferDetailsViewProtocol, TestableViewControllerProtocol { + + private var presenter: TransferDetailsPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + lazy var scrollView: UIScrollView = { + let scrollView = UIScrollView() + + view.addSubview(scrollView) + scrollView.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom) + make.left.bottom.right.equalToSuperview() + } + + return scrollView + }() + + lazy var contentView: UIView = { + let contentView = UIView() + scrollView.addSubview(contentView) + + contentView.snp.makeConstraints { (make) in + make.edges.equalToSuperview() + make.center.equalToSuperview() + } + + return contentView + }() + + // MARK: - Views + lazy var dateLabel: UILabel = { + let label = UILabel( + height: Consts.dateLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + label.textColor = UIColor.nynja.manatee + + contentView.addSubview(label) + + label.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom).offset(Consts.dateLabel.topOffset) + make.left.equalToSuperview().offset(Consts.dateLabel.horizontalInset) + make.width.equalToSuperview().dividedBy(Consts.dateLabel.widthDivider) + } + + return label + }() + + lazy var confirmationsLabel: UILabel = { + let label = UILabel( + height: Consts.confirmationsLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + label.textColor = UIColor.nynja.manatee + label.textAlignment = .right + + contentView.addSubview(label) + + label.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom).offset(Consts.confirmationsLabel.topOffset) + make.width.equalToSuperview().dividedBy(Consts.confirmationsLabel.widthDivider) + make.right.equalToSuperview().inset(Consts.confirmationsLabel.horizontalInset) + } + + return label + }() + + lazy var userPhotoImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.layer.masksToBounds = false + imageView.layer.cornerRadius = Consts.userPhotoImageView.sideSize / 2 + imageView.clipsToBounds = true + + contentView.addSubview(imageView) + + imageView.snp.makeConstraints({ (make) in + make.top.equalToSuperview().offset(Consts.userPhotoImageView.topOffset) + make.left.equalToSuperview().offset(Consts.userPhotoImageView.horizontalInset) + make.width.height.equalTo(Consts.userPhotoImageView.sideSize) + }) + + return imageView + }() + + lazy var nameLabel: UILabel = { + let label = UILabel( + height: Consts.confirmationsLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + label.textColor = UIColor.nynja.white + + contentView.addSubview(label) + + label.snp.makeConstraints { make in + make.height.equalTo(Consts.nameLabel.height) + make.bottom.equalTo(userPhotoImageView.snp.centerY) + make.left.equalTo(userPhotoImageView.snp.right).offset(Consts.nameLabel.horizontalInset) + make.right.equalTo(nynBalanceLabel.snp.left).offset(-Consts.nameLabel.horizontalInset) + } + + return label + }() + + lazy var senderLabel: UILabel = { + let label = UILabel( + height: Consts.confirmationsLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + label.textColor = UIColor.nynja.manatee + + contentView.addSubview(label) + + label.snp.makeConstraints { make in + make.height.equalTo(Consts.senderLabel.height) + make.top.equalTo(userPhotoImageView.snp.centerY) + make.left.equalTo(userPhotoImageView.snp.right).offset(Consts.senderLabel.horizontalInset) + make.right.equalTo(nynBalanceLabel.snp.left).offset(-Consts.senderLabel.horizontalInset) + } + + return label + }() + + lazy var nynBalanceLabel: UILabel = { + let label = UILabel( + height: Consts.nynBalanceLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.medium.name) + label.textAlignment = .right + + contentView.addSubview(label) + + label.snp.makeConstraints { make in + make.centerY.equalTo(userPhotoImageView) + make.height.equalTo(Consts.nynBalanceLabel.height) + make.right.equalToSuperview().inset(Consts.nynBalanceLabel.horizontalInset) + } + + return label + }() + + private lazy var notesTextView: UITextView = { + let textView = UITextView() + + textView.isUserInteractionEnabled = true + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false + + textView.textContainerInset = UIEdgeInsets.zero + textView.textContainer.lineFragmentPadding = Consts.notesTextView.lineFragmentPadding + textView.font = Consts.font + textView.backgroundColor = UIColor.clear + + contentView.addSubview(textView) + + textView.snp.makeConstraints { make in + make.top.equalTo(userPhotoImageView.snp.bottom).offset(Consts.notesTextView.inset) + make.left.right.equalToSuperview().inset(Consts.notesTextView.inset) + make.height.equalTo(Consts.notesTextView.height) + } + return textView + }() + + private lazy var addressTextField: MaterialTextField = { + + let textField = MaterialTextField() + textField.placeholder = "wallet_nynja_adress".localized + textField.isUserInteractionEnabled = false + textField.rightView = UIImageView(image: UIImage.nynja.Messages.ContextMenu.icCopyContextMenu.image) + textField.rightViewMode = UITextFieldViewMode.always + textField.cursorColor = UIColor.nynja.white + + contentView.addSubview(textField) + + textField.snp.makeConstraints { make in + make.top.equalTo(linkTextView.snp.bottom).offset(Consts.addressTextField.topOffset) + make.height.equalTo(Consts.addressTextField.height) + make.left.right.equalToSuperview().inset(Consts.addressTextField.leftInset) + } + + return textField + }() + + private lazy var blockchainExplorerBackView: UIView = { + let view = UIView() + + contentView.addSubview(view) + + view.snp.makeConstraints { (make) in + make.top.equalTo(notesTextView.snp.bottom) + make.left.right.equalToSuperview() + make.height.equalTo(Consts.blockchainExplorerBackView.height) + } + + return view + }() + + private lazy var blockchainExplorerLabel: UILabel = { + let label = UILabel( + height: Consts.dateLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + + blockchainExplorerBackView.addSubview(label) + + label.snp.makeConstraints { (make) in + make.centerY.equalToSuperview() + make.left.right.equalToSuperview().inset(Consts.blockchainExplorerLabel.horizontalInset) + } + + return label + }() + + private lazy var linkTextView: TextViewWithLinks = { + let textView = TextViewWithLinks() + + textView.isUserInteractionEnabled = true + textView.isScrollEnabled = false + textView.isEditable = false + textView.isSelectable = false + + textView.backgroundColor = UIColor.clear + textView.textContainerInset = UIEdgeInsets.zero + textView.textContainer.lineFragmentPadding = Consts.linkTextView.lineFragmentPadding + textView.font = Consts.font + + contentView.addSubview(textView) + + textView.snp.makeConstraints { make in + make.top.equalTo(blockchainExplorerBackView.snp.bottom).offset(Consts.linkTextView.topOffset) + make.left.right.equalToSuperview().inset(Consts.linkTextView.horizontalInset) + make.height.equalTo(Consts.linkTextView.height) + } + return textView + }() + + // MARK: - Life Cycle + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + } + + override func viewDidLoad() { + super.viewDidLoad() + presenter.showed() + } + + // MARK: - UI Setup + private func setupUI() { + + shouldShowSeparator = false + screenTitle = "wallet_transfer_details".localized.uppercased() + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: shouldShowSeparator, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + + + nynBalanceLabel.setContentCompressionResistancePriority( + UILayoutPriority.defaultHigh, + for: UILayoutConstraintAxis.horizontal) + senderLabel.setContentCompressionResistancePriority( + UILayoutPriority.defaultLow, + for: UILayoutConstraintAxis.horizontal) + nameLabel.setContentCompressionResistancePriority( + UILayoutPriority.defaultLow, + for: UILayoutConstraintAxis.horizontal) + + let nyn = NYNMoney(1000) + + blockchainExplorerBackView.backgroundColor = UIColor.nynja.black.withAlphaComponent(0.1) + blockchainExplorerLabel.text = "wallet_view_on_blockchain".localized + + userPhotoImageView.setImage(url: nil, placeHolder: UIImage.nynja.Contacts.avaPlaceholder.image) + + dateLabel.text = "Jul 25" + confirmationsLabel.text = "14 Confirmations" + nameLabel.text = "Rose Willis" + senderLabel.text = "Sender" + notesTextView.attributedText = + TransferDetailsViewController.makeAttributedNotes("Payment for translation of the some text") + nynBalanceLabel.text = nyn.formattedCurrencyTransaction + addressTextField.text = "0xdc9ec868ec813a927d0d3bc757081e29c69c23h432rg434" + + linkTextView.text = "https://www.facebook.com" + linkTextView.setupAutomatically() + linkTextView.isUnderlined = true + linkTextView.underlineColor = UIColor.nynja.blue + linkTextView.dataDetectorTypes = [.link] + } +} + +// MARK: - TestableViewControllerProtocol +extension TransferDetailsViewController { + + func setupTestingKeys() { + dateLabel.accessibilityIdentifier = "date_label" + confirmationsLabel.accessibilityIdentifier = "confirmations_label" + userPhotoImageView.accessibilityIdentifier = "user_photo_image_view" + nameLabel.accessibilityIdentifier = "name_label" + senderLabel.accessibilityIdentifier = "sender_label" + nynBalanceLabel.accessibilityIdentifier = "nyn_balance_label" + addressTextField.accessibilityIdentifier = "address_text_field" + notesTextView.accessibilityIdentifier = "notes_text_view" + blockchainExplorerBackView.accessibilityIdentifier = "blockchain_explorer_back_view" + blockchainExplorerLabel.accessibilityIdentifier = "blockchain_explorer_label" + linkTextView.accessibilityIdentifier = "link_text_view" + } +} + +extension TransferDetailsViewController: SetInjectable { + struct Dependencies { + let presenter: TransferDetailsPresenterProtocol + } + + func inject(dependencies: TransferDetailsViewController.Dependencies) { + presenter = dependencies.presenter + } +} + +private extension TransferDetailsViewController { + + enum Consts { + static let font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 18)! + enum dateLabel { + static let widthDivider = 2 + static let height = CGFloat(22.adjustedByWidth) + static let topOffset = CGFloat(6.0.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + } + + enum confirmationsLabel { + static let widthDivider = 2 + static let height = CGFloat(22.adjustedByWidth) + static let topOffset = CGFloat(6.0.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + } + + enum userPhotoImageView { + static let topOffset = CGFloat(36.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let sideSize = CGFloat(60.0.adjustedByWidth) + } + + enum nameLabel { + static let height = CGFloat(22.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let sideSize = CGFloat(60.0.adjustedByWidth) + } + + enum senderLabel { + static let height = CGFloat(22.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let sideSize = CGFloat(60.0.adjustedByWidth) + } + + enum nynBalanceLabel { + static let height = CGFloat(33.adjustedByWidth) + static let topOffset = CGFloat(8.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + } + + enum addressTextField { + static let height = CGFloat(65.adjustedByWidth) + static let topOffset = CGFloat(33.adjustedByWidth) + static let leftInset = CGFloat(16.adjustedByWidth) + } + + enum notesTextView { + static let height = CGFloat(64.adjustedByWidth) + static let inset = CGFloat(16.adjustedByWidth) + static let lineFragmentPadding = CGFloat(0) + } + + enum blockchainExplorerBackView { + static let height = CGFloat(32.adjustedByWidth) + } + + enum blockchainExplorerLabel { + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + } + + enum linkTextView { + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let height = CGFloat(22.adjustedByWidth) + static let topOffset = CGFloat(8.adjustedByWidth) + static let lineFragmentPadding = CGFloat(0) + } + } +} + +//MARK: Attributed text +private extension TransferDetailsViewController { + static func makeAttributedNotes(_ text: String) -> NSAttributedString? { + let finalString = NSMutableAttributedString() + let header = makeAttributedHeader(text: "wallet_notes_to_recipient".localized) + let notes = makeAttributedBody(text: text) + finalString.append(header) + finalString.append(attributedNewLine) + finalString.append(notes) + + return finalString + } + + static func makeAttributedHeader(text: String) -> NSAttributedString { + let font = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, height: 16)! + return NSMutableAttributedString( + string: text, + attributes: [ + NSAttributedStringKey.font: font, + NSAttributedStringKey.foregroundColor: UIColor.nynja.manatee]) + } + + static func makeAttributedBody(text: String) -> NSAttributedString { + let font = Consts.font + return NSMutableAttributedString( + string: text, + attributes: [ + NSAttributedStringKey.font: font, + NSAttributedStringKey.foregroundColor: UIColor.nynja.manatee]) + } + + static let attributedNewLine: NSAttributedString = { + let font = Consts.font + return NSMutableAttributedString(string: "\n", attributes: [NSAttributedStringKey.font: font]) + }() +} diff --git a/Nynja/Modules/Wallet Flows/TransferDetails/WireFrame/TransferDetailsWireFrame.swift b/Nynja/Modules/Wallet Flows/TransferDetails/WireFrame/TransferDetailsWireFrame.swift new file mode 100644 index 000000000..aa2cf0bb3 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferDetails/WireFrame/TransferDetailsWireFrame.swift @@ -0,0 +1,43 @@ +// +// TransferDetailsWireFrame.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class TransferDetailsWireFrame: TransferDetailsWireFrameProtocol { + + private weak var navigationController: UINavigationController? + + func presentTransferDetails(navigation: UINavigationController) { + navigationController = navigation + + let view = TransferDetailsViewController() + let presenter = TransferDetailsPresenter() + let interactor = TransferDetailsInteractor() + + // Module components + let viewDependencies = TransferDetailsViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + + let presenterDependencies = TransferDetailsPresenter.Dependencies( + view: view, + interactor: interactor, + wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + + let interactorDependencies = TransferDetailsInteractor.Dependencies(presenter: presenter) + interactor.inject(dependencies: interactorDependencies) + + navigationController?.pushViewController(view, animated: true) + } + + func close() { + navigationController?.popViewController(animated: true) + } +} + + diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/Entities/TransferHistoryTableModel.swift b/Nynja/Modules/Wallet Flows/TransferHistory/Entities/TransferHistoryTableModel.swift new file mode 100644 index 000000000..651ebca45 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/Entities/TransferHistoryTableModel.swift @@ -0,0 +1,24 @@ +// +// TransferHistoryTableModel.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +typealias TransferHistoryTableModel = [TransferHistoryTableSection] + +struct TransferHistoryTableSection { + struct Cell { + let name: String + let adress: String + let amount: NYNMoney + } + + let date: Date + let cells: [Cell] + + var dateDescription: String { + return WeakDays(initialDate: Date(), eventDate: date).day + } +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/Interactor/TransferHistoryInteractor.swift b/Nynja/Modules/Wallet Flows/TransferHistory/Interactor/TransferHistoryInteractor.swift new file mode 100644 index 000000000..8a553fdaa --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/Interactor/TransferHistoryInteractor.swift @@ -0,0 +1,42 @@ +// +// TransferHistoryInteractor.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/16/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class TransferHistoryInteractor: TransferHistoryInteractorInputProtocol { + + private weak var presenter: TransferHistoryInteractorOutputProtocol! + private var profile: Profile! + + func getTransfersHistory(completion: ((TransferHistoryTableModel) -> Void)) { + let model = TransferHistoryTableSection( + date: Date(), + cells: [ + TransferHistoryTableSection.Cell( + name: "Name Surname Surname Surname", + adress: "0xd23r276gfyegf26gfwhdshfr29r82nfkejfh473fbye84", + amount: NYNMoney(2342353465353450.5)) + ] + ) + + completion([model]) + } +} + +extension TransferHistoryInteractor: SetInjectable { + func inject(dependencies: TransferHistoryInteractor.Dependencies) { + presenter = dependencies.presenter + profile = dependencies.profile + } + + struct Dependencies { + let presenter: TransferHistoryInteractorOutputProtocol + let profile: Profile + } + +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/Presenter/TransferHistoryPresenter.swift b/Nynja/Modules/Wallet Flows/TransferHistory/Presenter/TransferHistoryPresenter.swift new file mode 100644 index 000000000..72e075c87 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/Presenter/TransferHistoryPresenter.swift @@ -0,0 +1,52 @@ +// +// TransferHistoryPresenter.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/16/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class TransferHistoryPresenter: BasePresenter, SetInjectable, TransferHistoryPresenterProtocol, +TransferHistoryInteractorOutputProtocol { + + private weak var view: TransferHistoryViewProtocol! + private var interactor: TransferHistoryInteractorInputProtocol! + private var wireFrame: TransferHistoryWireFrameProtocol! + + + // MARK: - TransferHistoryPresenterProtocol + + func showed() { + interactor.getTransfersHistory { [weak self] (model) in + if model.count > 0 { + self?.view.updateTable(with: model) + } else { + self?.view.showEmptyView() + } + } + } + + func openTransferDetails(with model: TransferHistoryTableSection.Cell) { + wireFrame.openTransferDetails() + } + + func back() { + wireFrame.close() + } +} + +extension TransferHistoryPresenter { + func inject(dependencies: TransferHistoryPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } + + struct Dependencies { + let view: TransferHistoryViewProtocol + let interactor: TransferHistoryInteractorInputProtocol + let wireFrame: TransferHistoryWireFrameProtocol + } +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/TransferHistoryProtocols.swift b/Nynja/Modules/Wallet Flows/TransferHistory/TransferHistoryProtocols.swift new file mode 100644 index 000000000..dabc41ae5 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/TransferHistoryProtocols.swift @@ -0,0 +1,41 @@ +// +// TransferHistoryProtocols.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/16/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol TransferHistoryWireFrameProtocol: class { + + func presentTransferHistory(navigation: UINavigationController, for profile: Profile) + func close() + func openTransferDetails() +} + +protocol TransferHistoryTableDataSourceProtocol: UITableViewDataSource, UITableViewDelegate { + func update(_ model: TransferHistoryTableModel) +} + +protocol TransferHistoryViewProtocol: class { + func updateTable(with model: TransferHistoryTableModel) + func showEmptyView() +} + +protocol TransferHistoryTableDataSourceOutputProtocol: class { + func didSelectCell(with model: TransferHistoryTableSection.Cell) +} + +protocol TransferHistoryPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + func showed() + func openTransferDetails(with model: TransferHistoryTableSection.Cell) +} + +protocol TransferHistoryInteractorOutputProtocol: class { +} + +protocol TransferHistoryInteractorInputProtocol: class { + func getTransfersHistory(completion: ((TransferHistoryTableModel) -> Void)) +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/View/TableView/TransferHistoryTableDataSource.swift b/Nynja/Modules/Wallet Flows/TransferHistory/View/TableView/TransferHistoryTableDataSource.swift new file mode 100644 index 000000000..fab31e065 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/View/TableView/TransferHistoryTableDataSource.swift @@ -0,0 +1,76 @@ +// +// TransferHistoryTableDataSource.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/18/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +final class TransferHistoryTableDataSource: NSObject, TransferHistoryTableDataSourceProtocol, SetInjectable { + + private weak var output: TransferHistoryTableDataSourceOutputProtocol! + + private var model: TransferHistoryTableModel = TransferHistoryTableModel() + + // MARK: + func update(_ model: TransferHistoryTableModel) { + self.model = model + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return model[section].cells.count + } + + func numberOfSections(in tableView: UITableView) -> Int { + return model.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellModel = model[indexPath.section].cells[indexPath.row] + guard let cell = tableView.dequeueReusableCell( + withIdentifier: TransferHistoryCell.cellId, + for: indexPath) as? TransferHistoryCell else { + assertionFailure("TransferHistoryCell cell not found") + return UITableViewCell() + } + + cell.userPhotoImageView.setImage(url: nil, placeHolder: UIImage.nynja.Contacts.avaPlaceholder.image) + cell.nameLabel.text = cellModel.name + cell.adressLabel.text = cellModel.adress + cell.moneyDiffLabel.text = cellModel.amount.formattedCurrencyTransaction + return cell + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return TransferHistoryViewController.Consts.tableView.cellHeight + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return TransferHistoryViewController.Consts.tableView.headerHeight + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let model = self.model[section] + + let headerView = tableView.dequeueReusableHeaderFooterView(ofType: TransferHistoryHeaderView.self) + headerView.titleLabel.text = model.dateDescription + headerView.isUserInteractionEnabled = false + + return headerView + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let cellModel = model[indexPath.section].cells[indexPath.row] + output.didSelectCell(with: cellModel) + } +} + +extension TransferHistoryTableDataSource { + func inject(dependencies: TransferHistoryTableDataSource.Dependencies) { + output = dependencies.output + } + + struct Dependencies { + let output: TransferHistoryTableDataSourceOutputProtocol + } +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryCell.swift b/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryCell.swift new file mode 100644 index 000000000..955a34b5c --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryCell.swift @@ -0,0 +1,120 @@ +// +// TransferHistoryCell.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/16/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +final class TransferHistoryCell: UITableViewCell { + + static var accessibilityPrefix = "transfer_history_cell" + static var cellId = "TransferHistoryCell" + + static private let nameFont = UIFont(name: FontFamily.NotoSans.regular.name, size: CGFloat(16.adjustedByWidth)) + static private let adressFont = UIFont(name: FontFamily.NotoSans.regular.name, size: CGFloat(14.adjustedByWidth)) + + // MARK: - Init + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + baseSetup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + baseSetup() + } + + private func baseSetup() { + backgroundColor = .clear + selectionStyle = .none + } + + // MARK: - Views + lazy var userPhotoImageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.layer.masksToBounds = false + imageView.layer.cornerRadius = Constraints.userPhotoViewSideSize / 2 + imageView.clipsToBounds = true + + contentView.addSubview(imageView) + + imageView.snp.makeConstraints({ (make) in + make.left.equalToSuperview().offset(Constraints.horizontalPadding) + make.centerY.equalToSuperview() + make.width.equalTo(Constraints.userPhotoViewSideSize) + make.height.equalTo(Constraints.userPhotoViewSideSize) + }) + + return imageView + }() + + lazy var nameLabel: UILabel = { + let lbl = UILabel() + lbl.textAlignment = .left + lbl.font = TransferHistoryCell.nameFont + lbl.textColor = UIColor.nynja.white + lbl.numberOfLines = 1 + lbl.baselineAdjustment = .alignCenters + + contentView.addSubview(lbl) + + lbl.snp.makeConstraints({ (make) in + make.top.equalTo(userPhotoImageView).offset(Constraints.topOffset) + make.left.equalTo(userPhotoImageView.snp.right).offset(Constraints.horizontalPadding) + make.right.equalTo(moneyDiffLabel.snp.left).offset(-Constraints.horizontalPadding) + }) + + return lbl + }() + + lazy var adressLabel: UILabel = { + let lbl = UILabel() + lbl.textAlignment = .left + lbl.font = TransferHistoryCell.adressFont + lbl.textColor = UIColor.nynja.manatee + lbl.numberOfLines = 1 + lbl.baselineAdjustment = .alignCenters + + contentView.addSubview(lbl) + + lbl.snp.makeConstraints({ (make) in + make.top.equalTo(nameLabel.snp.bottom) + make.left.equalTo(userPhotoImageView.snp.right).offset(Constraints.horizontalPadding) + make.right.equalToSuperview().inset(Constraints.addresHorizontalInset) + }) + + return lbl + }() + + lazy var moneyDiffLabel: UILabel = { + let lbl = UILabel() + lbl.textAlignment = .right + lbl.font = TransferHistoryCell.nameFont + lbl.textColor = UIColor.nynja.white + lbl.numberOfLines = 1 + lbl.baselineAdjustment = .alignCenters + + contentView.addSubview(lbl) + + lbl.snp.makeConstraints({ (make) in + make.top.equalTo(userPhotoImageView).offset(Constraints.topOffset) + make.right.equalToSuperview().inset(Constraints.horizontalPadding) + }) + + return lbl + }() +} + +extension TransferHistoryCell { + + struct Constraints { + static let horizontalPadding = 16.0.adjustedByWidth + static let topOffset = 3.0.adjustedByWidth + static let userPhotoViewSideSize = CGFloat(48.adjustedByWidth) + static let moneyDiffLabelWidth = CGFloat(100.adjustedByWidth) + static let addresHorizontalInset = 74.0.adjustedByWidth + } +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryHeaderView.swift b/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryHeaderView.swift new file mode 100644 index 000000000..8c3e5aeb8 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryHeaderView.swift @@ -0,0 +1,30 @@ +// +// TransferHistoryHeaderView.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 7/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import NynjaUIKit + +final class TransferHistoryHeaderView: UITableViewHeaderFooterView, Reusable { + + lazy var titleLabel: UILabel = { + + let label = UILabel( + height: 22, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name, + textAlignment: .left) + + contentView.addSubview(label) + label.snp.makeConstraints({ (make) in + make.centerY.equalToSuperview() + make.left.right.equalToSuperview().inset(16.adjustedByWidth) + }) + + return label + }() + +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryViewController.swift b/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryViewController.swift new file mode 100644 index 000000000..f8ba7366c --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/View/TransferHistoryViewController.swift @@ -0,0 +1,209 @@ +// +// TransferHistoryViewController.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/16/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit +import NynjaUIKit + +final class TransferHistoryViewController: BaseVC, TransferHistoryViewProtocol, +TransferHistoryTableDataSourceOutputProtocol, TestableViewControllerProtocol { + + private var tableDataSource: TransferHistoryTableDataSourceProtocol! + private var presenter: TransferHistoryPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + // MARK: - Life Cycle + + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + presenter.showed() + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationView.configure( + config: .init( + isVisibleSeparator: true, + isVisibleBackButton: true, + title: Constants.LocalizableKeys.transferHistory.localized.uppercased(), + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + } + + + // MARK: - UI Setup + + private func setupUI() { + screenTitle = Constants.LocalizableKeys.transferHistory.localized.uppercased() + + tableView.dataSource = tableDataSource + tableView.delegate = tableDataSource + historyEmptyLabel.text = "wallet_empty_history".localized + } + + // MARK: - Views + + lazy var historyEmptyImageView: UIImageView = { + let imageView = UIImageView(image: UIImage.nynja.Wallet.icTransferHistoryEmpty.image) + view.addSubview(imageView) + imageView.snp.makeConstraints({ (make) in + make.center.equalToSuperview() + }) + + return imageView + }() + + lazy var historyEmptyLabel: UILabel = { + let label = UILabel( + height: CGFloat(Consts.historyEmptyLabel.height), + color: UIColor.nynja.manatee, + fontName: FontFamily.NotoSans.medium.name, + textAlignment: .center) + + label.numberOfLines = Consts.historyEmptyLabel.numberOfLines + label.adjustsFontSizeToFitWidth = true + + view.addSubview(label) + label.snp.makeConstraints { maker in + maker.left.right.equalToSuperview().inset(Consts.historyEmptyLabel.horizontalInset) + maker.top.equalTo(historyEmptyImageView.snp.bottom) + } + + return label + }() + + lazy var tableView: UITableView = { + let tableView = UITableView(frame: CGRect.zero, style: .grouped) + + tableView.backgroundColor = .clear + tableView.clipsToBounds = true + tableView.separatorColor = UIColor.nynja.backgroundGray + tableView.separatorInset = UIEdgeInsetsMake(0, + Consts.tableView.separatorInset, + 0, + Consts.tableView.separatorInset) + + tableView.keyboardDismissMode = .onDrag + tableView.separatorStyle = .singleLine + tableView.separatorColor = UIColor.nynja.backgroundGray + tableView.separatorInset = Constants.tableView.defaultSeparatorInset + + tableView.register(TransferHistoryCell.self, forCellReuseIdentifier: TransferHistoryCell.cellId) + tableView.register(headerFooter: TransferHistoryHeaderView.self) + + view.addSubview(tableView) + + tableView.snp.makeConstraints({ (make) in + make.left.right.equalToSuperview() + make.top.equalTo(navigationView.snp.bottom) + }) + return tableView + }() + + private lazy var controlContainerView: NynjaControlContainerView = { + let containerView = NynjaControlContainerView(contentView: searchField) + + view.addSubview(containerView) + containerView.snp.makeConstraints { maker in + maker.left.right.equalToSuperview() + maker.top.equalTo(self.tableView.snp.bottom) + adjustVerticalInset(.bottom, make: maker, offset: -Consts.controlContainerView.horizontalOffset) + } + + containerView.addGradientView() + + return containerView + }() + + private lazy var searchField: NynjaSearchField = { + let searchField = NynjaSearchField() + + searchField.searchTextChangeHandler = { [weak self] searchQuery in + } + searchField.returnHandler = { [weak self] in + self?.view.endEditing(true) + } + + return searchField + }() + + // MARK: TransferHistoryViewProtocol + func updateTable(with model: TransferHistoryTableModel) { + tableView.isHidden = false + controlContainerView.isHidden = false + historyEmptyLabel.isHidden = true + historyEmptyImageView.isHidden = true + + tableDataSource.update(model) + tableView.reloadData() + } + + func showEmptyView() { + tableView.isHidden = true + controlContainerView.isHidden = true + historyEmptyLabel.isHidden = false + historyEmptyImageView.isHidden = false + } +} + +// MARK: - TestableViewControllerProtocol +extension TransferHistoryViewController { + + func setupTestingKeys() { + + } +} + +extension TransferHistoryViewController: SetInjectable { + func inject(dependencies: TransferHistoryViewController.Dependencies) { + presenter = dependencies.presenter + tableDataSource = dependencies.tableDataSource + } + + struct Dependencies { + let presenter: TransferHistoryPresenterProtocol + let tableDataSource: TransferHistoryTableDataSourceProtocol + } +} + +extension TransferHistoryViewController { + enum Consts { + enum tableView { + static let separatorInset = CGFloat(16.adjustedByWidth) + static let cellHeight = CGFloat(64.adjustedByWidth) + static let headerHeight = CGFloat(36.adjustedByWidth) + } + enum controlContainerView { + static let horizontalOffset = CGFloat(50.adjustedByWidth) + } + + enum historyEmptyLabel { + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let height = CGFloat(66.adjustedByWidth) + static let numberOfLines = 3 + } + + enum nextButton { + static let horizontalInset = CGFloat(16.adjustedByWidth) + static let height = CGFloat(44.0.adjustedByWidth) + static let bottomOffset = CGFloat(28.0.adjustedByWidth) + } + } +} + +//MARK: TransferHistoryTableDataSourceOutputProtocol +extension TransferHistoryViewController { + func didSelectCell(with model: TransferHistoryTableSection.Cell) { + presenter.openTransferDetails(with: model) + } +} diff --git a/Nynja/Modules/Wallet Flows/TransferHistory/WireFrame/TransferHistoryWireFrame.swift b/Nynja/Modules/Wallet Flows/TransferHistory/WireFrame/TransferHistoryWireFrame.swift new file mode 100644 index 000000000..0450aa975 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/TransferHistory/WireFrame/TransferHistoryWireFrame.swift @@ -0,0 +1,54 @@ +// +// TransferHistoryWireFrame.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/16/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class TransferHistoryWireFrame: TransferHistoryWireFrameProtocol { + + private weak var navigationController: UINavigationController? + + func presentTransferHistory(navigation: UINavigationController, for profile: Profile) { + navigationController = navigation + + // Module components + let view = TransferHistoryViewController() + let presenter = TransferHistoryPresenter() + let interactor = TransferHistoryInteractor() + let tableDataSource = TransferHistoryTableDataSource() + + let dataSourceDependencies = TransferHistoryTableDataSource.Dependencies(output: view) + tableDataSource.inject(dependencies: dataSourceDependencies) + + + // Connecting + let viewDependencies = TransferHistoryViewController.Dependencies( + presenter: presenter, + tableDataSource: tableDataSource) + view.inject(dependencies: viewDependencies) + + let presenterDependencies = TransferHistoryPresenter.Dependencies( + view: view, + interactor: interactor, + wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + + let interactorDependencies = TransferHistoryInteractor.Dependencies(presenter: presenter, profile: profile) + interactor.inject(dependencies: interactorDependencies) + + navigation.pushViewController(view, animated: true) + } + + func close() { + navigationController?.popViewController(animated: true) + } + + func openTransferDetails() { + guard let navigationController = navigationController else { return } + TransferDetailsWireFrame().presentTransferDetails(navigation: navigationController) + } +} diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesOutput.swift b/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesOutput.swift new file mode 100644 index 000000000..fbab66727 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesOutput.swift @@ -0,0 +1,13 @@ +// +// WalletBalancesOutput.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 8/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct WalletBalancesOutput { + let nynBalance: NYNMoney + let address: String + let profile: Profile +} diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesViewModel.swift b/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesViewModel.swift new file mode 100644 index 000000000..734f33fde --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesViewModel.swift @@ -0,0 +1,30 @@ +// +// WalletBalancesViewModel.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +typealias WalletBalancesViewModel = [WalletBalancesViewSection] + +struct WalletBalancesViewSection { + let title: String + let cells: [Cell] + + struct Cell { + let titleText: String + let bodyText: String + let imageName: String + let disabled: Bool + + init(titleText: String, bodyText: String, imageName: String, disabled: Bool = false) { + self.titleText = titleText + self.bodyText = bodyText + self.imageName = imageName + self.disabled = disabled + } + } +} diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesWallet.swift b/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesWallet.swift new file mode 100644 index 000000000..7dd2e1db8 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletBalances/Entities/WalletBalancesWallet.swift @@ -0,0 +1,14 @@ +// +// WalletBalancesWallet.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct WalletBalancesWallet { + let nynjaCoin: NYNMoney + let nynjaWalletAddress: String + let ethereum: Money? + let name: String? +} diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift b/Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift new file mode 100644 index 000000000..eac31c52f --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift @@ -0,0 +1,90 @@ +// +// WalletBalancesInteractor.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class WalletBalancesInteractor: WalletBalancesInteractorInputProtocol, SetInjectable { + private weak var presenter: WalletBalancesInteractorOutputProtocol! + private var profile: Profile! + private var walletService: WalletServiceProtocol! + private var profileServices: ProfileServicesProtocol! + private var inputValidationService: WalletOpeningTextInputValidationServiceProtocol! + private var wallet: WalletBalancesWallet? + + //MARK: WalletBalancesInteractorInputProtocol + func getWallet(name: String?, passcode: String, completion: @escaping (WalletBalancesWallet?, Error?) -> ()) { + fetchWallet(name: name, passcode: passcode) { [weak self] (walletResult, error) in + if walletResult != nil { + self?.wallet = walletResult + } + + completion(walletResult, error) + } + } + + func getFetchedWallet() -> WalletBalancesWallet? { + return wallet + } + + func getProfile() -> Profile { + return profile + } + + func getWalletName() -> String? { + // Wallet inplementation should be changed on server side, until this wallet name will be stored in password field + return profileServices.wallet?.password + } + + func validate(passcode: String) -> Bool { + let result = inputValidationService.validateWallet(passcode: passcode) + switch result { + case .success: + return true + case .failure: + return false + } + } +} + +private extension WalletBalancesInteractor { + func fetchWallet(name: String?, passcode: String, completion: @escaping (WalletBalancesWallet?, Error?) -> ()) { + walletService.getWallet(name: name, passcode: passcode) { (result) in + switch result { + case .success(let walletFromSDK): + let amount = Decimal(string: walletFromSDK.account.balance)! + let wallet = WalletBalancesWallet( + nynjaCoin: NYNMoney(amount), + nynjaWalletAddress: walletFromSDK.account.address, + ethereum: nil, + name: walletFromSDK.name) + completion(wallet, nil) + case .failure(let error): + completion(nil, error) + } + } + } +} + +extension WalletBalancesInteractor { + struct Dependencies { + let presenter: WalletBalancesInteractorOutputProtocol + let profile: Profile + let walletService: WalletServiceProtocol + let profileServices: ProfileServicesProtocol + let inputValidationService: WalletOpeningTextInputValidationServiceProtocol + } + + func inject(dependencies: WalletBalancesInteractor.Dependencies) { + presenter = dependencies.presenter + profile = dependencies.profile + walletService = dependencies.walletService + profileServices = dependencies.profileServices + inputValidationService = dependencies.inputValidationService + } +} + diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/Presenter/WalletBalancesPresenter.swift b/Nynja/Modules/Wallet Flows/WalletBalances/Presenter/WalletBalancesPresenter.swift new file mode 100644 index 000000000..f4b04878d --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletBalances/Presenter/WalletBalancesPresenter.swift @@ -0,0 +1,130 @@ +// +// WalletBalancesPresenter.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class WalletBalancesPresenter: BasePresenter, WalletBalancesPresenterProtocol, +WalletBalancesInteractorOutputProtocol, SetInjectable { + + enum ImageNames: String { + case nynjaCoinIcon = "ic_nyn" + case ethIcon = "ic_eth_disabled" + } + + // MARK: NavigationProtocol + func back() { + wireFrame.close() + } + + private weak var view: WalletBalancesViewProtocol! + private var interactor: WalletBalancesInteractorInputProtocol! + private var wireFrame: WalletBalancesWireFrameProtocol! + + + // MARK: - WalletBalancesPresenterProtocol + + func passcodeAlertTextSholdChange(text: String?, range: NSRange, string: String) -> Bool { + let result = String.replace(in: text, range: range, string: string) + return interactor.validate(passcode: result) + } + + func showed() { + let name = interactor.getWalletName() + if name != nil { + view.setupPassocdeInput() + } else { + view.setupCreateWallet() + } + } + + func setupWalletBalances(passcode: String) { + view.showWalletFetchInProgress() + let name = interactor.getWalletName() + interactor.getWallet(name: name, passcode: passcode) { [weak self] (wallet, error) in + guard let `self` = self else { + return + } + + dispatchAsyncMain { + self.view.showWalletFetchEnded() + if let wallet = wallet { + let viewModel = self.makeViewModel(with: [wallet]) + self.view.setup(with: viewModel) + } else { + self.wireFrame.close() + } + } + } + } + + func createWalletTriggered() { + let profile = interactor.getProfile() + wireFrame.openCreateWalletScren(for: profile) + } + + func openWallet() { + guard let wallet = interactor.getFetchedWallet() else { + return + } + + let profile = interactor.getProfile() + wireFrame.openWallet(with: WalletBalancesOutput( + nynBalance: wallet.nynjaCoin, + address: wallet.nynjaWalletAddress, + profile: profile + ) + ) + } + + func passcodeInputCanceled() { + wireFrame.close() + } +} + +extension WalletBalancesPresenter { + struct Dependencies { + let view: WalletBalancesViewProtocol + let interactor: WalletBalancesInteractorInputProtocol + let wireFrame: WalletBalancesWireFrameProtocol + } + + func inject(dependencies: WalletBalancesPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } +} + +private extension WalletBalancesPresenter { + + func makeViewCellModel(with money: Money) -> + WalletBalancesViewSection.Cell { + return WalletBalancesViewSection.Cell( + titleText: money.currency.code, + bodyText: money.formattedDecimal ?? money.description, + imageName: ImageNames.nynjaCoinIcon.rawValue) + } + + func makeViewModel(with model: [WalletBalancesWallet]) -> WalletBalancesViewModel { + + let viewModel = model.map { (wallet) -> WalletBalancesViewSection in + let nyn = makeViewCellModel(with: wallet.nynjaCoin) + + let ethereum = WalletBalancesViewSection.Cell( + titleText: ETH.code, + bodyText: "wallet_coming_soon".localized, + imageName: ImageNames.ethIcon.rawValue, + disabled: true) + + let title = wallet.name ?? "" + return WalletBalancesViewSection(title: title, cells: [nyn, ethereum]) + } + + return viewModel + } +} diff --git a/Nynja/Modules/WalletBalances/View/WalletBalancesTableViewCell.swift b/Nynja/Modules/Wallet Flows/WalletBalances/View/WalletBalancesTableViewCell.swift similarity index 89% rename from Nynja/Modules/WalletBalances/View/WalletBalancesTableViewCell.swift rename to Nynja/Modules/Wallet Flows/WalletBalances/View/WalletBalancesTableViewCell.swift index 156327742..6d845c5af 100644 --- a/Nynja/Modules/WalletBalances/View/WalletBalancesTableViewCell.swift +++ b/Nynja/Modules/Wallet Flows/WalletBalances/View/WalletBalancesTableViewCell.swift @@ -8,16 +8,10 @@ import Foundation -struct WalletBalancesTableViewCellModel { - let titleText: String - let bodyText: String - let imageName: String -} - class WalletBalancesTableViewCell: UITableViewCell { static let cellId = "WalletBalancesTableViewCell" - static let height = CGFloat(65.0.adjustedByWidth) + static let height = CGFloat(64.0.adjustedByWidth) private struct Constraints { @@ -25,21 +19,21 @@ class WalletBalancesTableViewCell: UITableViewCell { static let topOffset: CGFloat = CGFloat(11.0.adjustedByWidth) static let height: CGFloat = CGFloat(22.0.adjustedByWidth) static let width: CGFloat = CGFloat(200.adjustedByWidth) - static let leftInset = 38.0.adjustedByWidth + static let leftInset = 16.0.adjustedByWidth } struct bodyLabel { static let height: CGFloat = CGFloat(20.0.adjustedByWidth) static let width: CGFloat = CGFloat(200.adjustedByWidth) - static let leftInset = 38.0.adjustedByWidth + static let leftInset = 16.0.adjustedByWidth } struct cryptoIconImageView { static let width: CGFloat = CGFloat(24.0.adjustedByWidth) static let height: CGFloat = CGFloat(20.0.adjustedByWidth) - static let leftInset = 33.0.adjustedByWidth + static let leftInset = 16.0.adjustedByWidth } } diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/View/WalletBalancesViewController.swift b/Nynja/Modules/Wallet Flows/WalletBalances/View/WalletBalancesViewController.swift new file mode 100644 index 000000000..b2af62987 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletBalances/View/WalletBalancesViewController.swift @@ -0,0 +1,349 @@ +// +// WalletBalancesViewController.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class WalletBalancesViewController: BaseVC, WalletBalancesViewProtocol, SetInjectable, +TestableViewControllerProtocol, UITextFieldDelegate { + + private weak var textField: UITextField? + private var model = WalletBalancesViewModel() + + private var presenter: WalletBalancesPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + lazy var swipeBackHelper: SwipeBackHelper = { + return SwipeBackHelper(with: self, gestureCompletion: nil) + }() + + // MARK: - Views + + private lazy var emptyLabel: UILabel = { + + let label = UILabel( + height: Consts.emptyLabel.height, + color: UIColor.nynja.manatee, + fontName: FontFamily.NotoSans.medium.name) + label.text = "wallet_dont_have_any".localized + label.numberOfLines = Consts.emptyLabel.numberOfLines + label.textAlignment = .center + + view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.width.equalToSuperview().offset(-Consts.emptyLabel.widthOffset) + make.centerX.equalToSuperview() + make.top.equalTo(emptyIcon.snp.bottom).offset(Consts.emptyLabel.topOffset) + }) + + return label + + }() + + private lazy var emptyIcon: UIImageView = { + let icon = UIImageView(image: UIImage.nynja.Wallet.icWalletAbsent.image) + + view.addSubview(icon) + icon.snp.makeConstraints({ (make) in + make.center.equalToSuperview() + }) + + return icon + }() + + private lazy var createWalletButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("wallet_create".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(createWalletAction), for: .touchUpInside) + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(Consts.createWalletButton.height) + make.left.right.equalToSuperview().inset(Consts.createWalletButton.horizontalInset) + make.bottom.equalToSuperview().offset(-Consts.createWalletButton.bottomOffset) + } + + return button + }() + + private lazy var tableView: UITableView = { + let table = UITableView(frame: CGRect.zero, style: .grouped) + table.delegate = self + table.dataSource = self + table.backgroundColor = UIColor.nynja.clear + table.separatorStyle = .singleLine + table.separatorColor = UIColor.nynja.backgroundGray + table.tableFooterView = UIView() + table.clipsToBounds = true + table.isScrollEnabled = false + table.separatorInset = Consts.tableView.separatorInset + + table.register(WalletBalancesTableViewCell.self, forCellReuseIdentifier: WalletBalancesTableViewCell.cellId) + + view.addSubview(table) + table.snp.makeConstraints({ (make) in + make.top.equalTo(navigationView.snp.bottom) + make.left.right.bottom.equalToSuperview() + make.bottom.equalToSuperview() + }) + + return table + }() + + // MARK: - Life Cycle + + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + presenter.showed() + } + + // MARK: - UI Setup + private func setupUI() { + setupNavigationView() + } + + private func setupNavigationView() { + shouldShowSeparator = true + screenTitle = "wallet_header".localized.uppercased() + + navigationView.configure(config: .init( + isVisibleSeparator: shouldShowSeparator, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + } + + //MARK: Actions + @objc private func createWalletAction() { + view.endEditing(true) + presenter.createWalletTriggered() + } +} + +// MARK: - Testable +extension WalletBalancesViewController { + + func setupTestingKeys() { + tableView.accessibilityIdentifier = "table_view" + emptyIcon.accessibilityIdentifier = "empty_icon" + emptyLabel.accessibilityIdentifier = "empty_label" + createWalletButton.accessibilityIdentifier = "create_wallet_button" + } +} + +extension WalletBalancesViewController { + struct Dependencies { + let presenter: WalletBalancesPresenterProtocol + } + + func inject(dependencies: WalletBalancesViewController.Dependencies) { + presenter = dependencies.presenter + } +} + +// MARK: WalletBalancesViewProtocol +extension WalletBalancesViewController { + + func setup(with model: WalletBalancesViewModel) { + showWalletTable() + self.model = model + tableView.reloadData() + } + + func setupCreateWallet() { + showCreateWalletViews() + } + + func showWalletFetchInProgress() { + showSpinner() + } + + func showWalletFetchEnded() { + hideSpinner() + } + + func setupPassocdeInput() { + hideAllViews() + showPasscodeAlert() + } +} + +private extension WalletBalancesViewController { + + func showWalletTable() { + tableView.isHidden = false + emptyIcon.isHidden = true + emptyLabel.isHidden = true + createWalletButton.isHidden = true + } + + func showCreateWalletViews() { + tableView.isHidden = true + emptyIcon.isHidden = false + emptyLabel.isHidden = false + createWalletButton.isHidden = false + } + + func hideAllViews() { + tableView.isHidden = true + emptyIcon.isHidden = true + emptyLabel.isHidden = true + createWalletButton.isHidden = true + } + + func showPasscodeAlert() { + AlertManager.sharedInstance.showAlertOkCancel( + title: "enter_passcode".localized, + message: "", + textFieldConfig: { [weak self] textField in + textField.keyboardType = .numberPad + textField.delegate = self + textField.isSecureTextEntry = true + self?.textField = textField + }, + completionOk: { [weak self] action in + if let text = self?.textField?.text { + self?.presenter.setupWalletBalances(passcode: text) + } else { + self?.presenter.passcodeInputCanceled() + } + }, + completionCancel: { [weak self] action in + self?.presenter.passcodeInputCanceled() + } + ) + } +} + +//MARK: UITextFieldDelegate +extension WalletBalancesViewController { + func textField(_ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + return presenter.passcodeAlertTextSholdChange(text: textField.text, range: range, string: string) + } +} + +extension WalletBalancesViewController: UITableViewDelegate, UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return model.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return model[section].cells.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellModel = model[indexPath.section].cells[indexPath.row] + + var cellOptional: UITableViewCell? { + let cell = tableView.dequeueReusableCell(withIdentifier: WalletBalancesTableViewCell.cellId, + for: indexPath) as? WalletBalancesTableViewCell + cell?.titleLabel.text = cellModel.titleText + cell?.titleLabel.textColor = + cellModel.disabled ? UIColor.nynja.manatee : UIColor.nynja.white + cell?.bodyLabel.text = cellModel.bodyText + cell?.cryptoIconImageView.image = UIImage(named: cellModel.imageName) + cell?.isUserInteractionEnabled = true + return cell + } + + guard let cell = cellOptional else { + assertionFailure("Cell should be constuct") + return UITableViewCell(frame: CGRect.zero) + } + + cell.backgroundColor = UIColor.nynja.clear + cell.selectionStyle = .none + + return cell + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let view = UIView() + + let titleLabel = UILabel( + height: Consts.tableView.headerView.titleLabelHeight, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + titleLabel.text = model[section].title + + view.addSubview(titleLabel) + titleLabel.snp.makeConstraints { (make) in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(Consts.tableView.headerView.horizontalOffset) + } + + let button = UIButton(type: .custom) + button.isUserInteractionEnabled = false + button.setImage(UIImage.nynja.icArrowDown.image, for: .normal) + + view.addSubview(button) + button.snp.makeConstraints { (make) in + make.centerY.equalToSuperview() + make.right.equalToSuperview().offset(-Consts.tableView.headerView.horizontalOffset) + } + + return view + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return Consts.tableView.heightForHeaderInSection + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return WalletBalancesTableViewCell.height + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let _ = tableView.cellForRow(at: indexPath) as? WalletBalancesTableViewCell else { + return + } + presenter.openWallet() + } +} + +private extension WalletBalancesViewController { + enum Consts { + enum tableView { + static let separatorInset = UIEdgeInsetsMake(0, + CGFloat(16.adjustedByWidth), + 0, + CGFloat(16.adjustedByWidth)) + static let heightForHeaderInSection = CGFloat(64.adjustedByWidth) + + enum headerView { + static let titleLabelHeight = CGFloat(23) + static let horizontalOffset = CGFloat(16.adjustedByWidth) + } + } + + enum emptyLabel { + static let height = CGFloat(23.adjustedByWidth) + static let numberOfLines = 2 + static let widthOffset = CGFloat(28) + static let topOffset = CGFloat(16.adjustedByWidth) + } + + enum createWalletButton { + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let height = CGFloat(44.0.adjustedByWidth) + static let bottomOffset = CGFloat(28) + + } + } +} diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/WalletBalancesProtocols.swift b/Nynja/Modules/Wallet Flows/WalletBalances/WalletBalancesProtocols.swift new file mode 100644 index 000000000..967cd8b40 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletBalances/WalletBalancesProtocols.swift @@ -0,0 +1,47 @@ +// +// WalletBalancesProtocols.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 6/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol WalletBalancesWireFrameProtocol: class { + + func presentWalletBalances(for profile: Profile, navigationController: UINavigationController) + func openCreateWalletScren(for profile: Profile) + func close() + func openWallet(with params: WalletBalancesOutput) +} + +protocol WalletBalancesViewProtocol: class { + + func showWalletFetchInProgress() + func showWalletFetchEnded() + func setup(with model: WalletBalancesViewModel) + func setupCreateWallet() + func setupPassocdeInput() +} + +protocol WalletBalancesPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + + func showed() + func createWalletTriggered() + func openWallet() + func passcodeAlertTextSholdChange(text: String?, range: NSRange, string: String) -> Bool + func passcodeInputCanceled() + func setupWalletBalances(passcode: String) +} + +protocol WalletBalancesInteractorOutputProtocol: class { +} + +protocol WalletBalancesInteractorInputProtocol: class { + func getWallet(name: String?, passcode: String, completion: @escaping (WalletBalancesWallet?, Error?) -> ()) + func getFetchedWallet() -> WalletBalancesWallet? + func getProfile() -> Profile + func getWalletName() -> String? + func validate(passcode: String) -> Bool +} diff --git a/Nynja/Modules/WalletBalances/WireFrame/WalletBalancesWireFrame.swift b/Nynja/Modules/Wallet Flows/WalletBalances/WireFrame/WalletBalancesWireFrame.swift similarity index 50% rename from Nynja/Modules/WalletBalances/WireFrame/WalletBalancesWireFrame.swift rename to Nynja/Modules/Wallet Flows/WalletBalances/WireFrame/WalletBalancesWireFrame.swift index 20c5418dc..0b5913d1b 100644 --- a/Nynja/Modules/WalletBalances/WireFrame/WalletBalancesWireFrame.swift +++ b/Nynja/Modules/Wallet Flows/WalletBalances/WireFrame/WalletBalancesWireFrame.swift @@ -11,6 +11,7 @@ import UIKit final class WalletBalancesWireFrame: WalletBalancesWireFrameProtocol { weak var navigationController: UINavigationController? + private let serviceFactory = ServiceFactory() func presentWalletBalances(for profile: Profile, navigationController: UINavigationController) { self.navigationController = navigationController @@ -23,24 +24,43 @@ final class WalletBalancesWireFrame: WalletBalancesWireFrameProtocol { let viewDependencies = WalletBalancesViewController.Dependencies(presenter: presenter) view.inject(dependencies: viewDependencies) - let presenterDependencies = WalletBalancesPresenter.Dependencies(view: view, interactor: interactor, wireFrame: self) + let presenterDependencies = WalletBalancesPresenter.Dependencies( + view: view, + interactor: interactor, + wireFrame: self) presenter.inject(dependencies: presenterDependencies) - let profile = ProfileDAO.currentProfile - let interactorDependencies = WalletBalancesInteractor.Dependencies(presenter: presenter, profile: profile) + let inputValidationService = serviceFactory.makeWalletOpeningTextInputValidationService() + let walletService = serviceFactory.makeWalletService() + let interactorDependencies = WalletBalancesInteractor.Dependencies( + presenter: presenter, + profile: profile, + walletService: walletService, + profileServices: ProfileServices(profile: profile), + inputValidationService: inputValidationService) interactor.inject(dependencies: interactorDependencies) - // Connecting - view.presenter = presenter - presenter.view = view - presenter.wireFrame = self - presenter.interactor = interactor - interactor.presenter = presenter - navigationController.pushViewController(view, animated: true) } func close() { navigationController?.popViewController(animated: true) } + + func openCreateWalletScren(for profile: Profile) { + guard let navigationController = navigationController else { return } + CreateWalletWireFrame().presentCreateWallet(navigation: navigationController, for: profile) + } + + func openWallet(with params: WalletBalancesOutput) { + guard let navigationController = navigationController else { return } + WalletDetailsWireFrame().presentWalletDetails( + navigation: navigationController, + for: params.profile, + input: WalletDetailsViewInput( + cryptoMoney: params.nynBalance, + address: params.address + ) + ) + } } diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/Entities/WalletDetailsViewInput.swift b/Nynja/Modules/Wallet Flows/WalletDetails/Entities/WalletDetailsViewInput.swift new file mode 100644 index 000000000..aad69f760 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/Entities/WalletDetailsViewInput.swift @@ -0,0 +1,12 @@ +// +// WalletDetailsViewInput.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 8/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct WalletDetailsViewInput { + let cryptoMoney: NYNMoney + let address: String +} diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift b/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift new file mode 100644 index 000000000..0789143a6 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift @@ -0,0 +1,55 @@ +// +// WalletDetailsInteractor.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class WalletDetailsInteractor: WalletDetailsInteractorInputProtocol { + + private weak var presenter: WalletDetailsInteractorOutputProtocol! + private var profile: Profile! + private var profileServices: ProfileServices! + private var input: WalletDetailsViewInput! + + //MARK: WalletDetailsInteractorInputProtocol + func fetchQRCodeImageData(completion: ((Data?) -> Void)) { + let data = input.address.data(using: String.Encoding.utf8) + completion(data) + } + + func getNynjaCoinBalance() -> NYNMoney { + return input.cryptoMoney + } + + func getUSDEquivalent() -> Money { + return Money(input.cryptoMoney.amount * 0.125) + } + + func getWalletAdress() -> String { + return input.address + } + + func getProfile() -> Profile { + return profile + } +} + +extension WalletDetailsInteractor: SetInjectable { + struct Dependencies { + let presenter: WalletDetailsInteractorOutputProtocol + let profile: Profile + let profileServices: ProfileServices + let input: WalletDetailsViewInput + } + + func inject(dependencies: WalletDetailsInteractor.Dependencies) { + presenter = dependencies.presenter + profile = dependencies.profile + profileServices = dependencies.profileServices + input = dependencies.input + } +} diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/Presenter/WalletDetailsPresenter.swift b/Nynja/Modules/Wallet Flows/WalletDetails/Presenter/WalletDetailsPresenter.swift new file mode 100644 index 000000000..1d15f47e9 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/Presenter/WalletDetailsPresenter.swift @@ -0,0 +1,71 @@ +// +// WalletDetailsPresenter.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class WalletDetailsPresenter: BasePresenter, WalletDetailsPresenterProtocol, WalletDetailsInteractorOutputProtocol { + + private weak var view: WalletDetailsViewProtocol! + private var interactor: WalletDetailsInteractorInputProtocol! + private var wireFrame: WalletDetailsWireFrameProtocol! + + + // MARK: - WalletDetailsPresenterProtocol + + func formattedCurrencyBalance() -> String? { + let currencyBalance = interactor.getNynjaCoinBalance() + return currencyBalance.formattedCurrency + } + + func formattedUSDEquivalent() -> String? { + let usd = interactor.getUSDEquivalent() + let formatter = NumberFormatter() + formatter.numberStyle = .currency + formatter.currencyCode = usd.currency.code + + return formatter.string(for: usd.amount) + } + + func walletAdress() -> String { + return interactor.getWalletAdress() + } + + func showed() { + interactor.fetchQRCodeImageData { [weak self] (data) in + self?.view.setupQRCodeWith(data) + } + } + + func openTransferHistory() { + let profile = interactor.getProfile() + wireFrame.openTransferHistory(for: profile) + } + + func openContacts() { + wireFrame.openContacts() + } + + //MARK: NavigationProtocol + func back() { + wireFrame.close() + } +} + +extension WalletDetailsPresenter: SetInjectable { + struct Dependencies { + let view: WalletDetailsViewProtocol + let interactor: WalletDetailsInteractorInputProtocol + let wireFrame: WalletDetailsWireFrameProtocol + } + + func inject(dependencies: WalletDetailsPresenter.Dependencies) { + view = dependencies.view + interactor = dependencies.interactor + wireFrame = dependencies.wireFrame + } +} diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/View/WalletDetailsViewController.swift b/Nynja/Modules/Wallet Flows/WalletDetails/View/WalletDetailsViewController.swift new file mode 100644 index 000000000..d10c33a42 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/View/WalletDetailsViewController.swift @@ -0,0 +1,286 @@ +// +// WalletDetailsViewController.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit +import QRCode + +final class WalletDetailsViewController: BaseVC, WalletDetailsViewProtocol, TestableViewControllerProtocol { + + private var presenter: WalletDetailsPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + // MARK: - Views + + private lazy var historyButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("history_title".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(historyButtonAction), for: .touchUpInside) + + button.defaultColor = UIColor.clear + button.highlightedColor = UIColor.clear + button.isHighlighted = false + button.layer.borderWidth = Constraints.historyButton.borderWidth + button.layer.borderColor = UIColor.nynja.mainRed.cgColor + + button.setTitleColor(UIColor.nynja.mainRed, for: .normal) + button.setTitleColor(UIColor.nynja.mainRed, for: .disabled) + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(Constraints.historyButton.height) + make.width.equalTo(Constraints.historyButton.width) + make.left.equalToSuperview().inset(Constraints.historyButton.horizontalInset) + make.bottom.equalToSuperview().inset(Constraints.historyButton.bottomInset) + } + + return button + }() + + private lazy var sendButton: NynjaButton = { + let button = NynjaButton() + + button.setTitle("wallet_send".localized.uppercased(), for: .normal) + button.addTarget(self, action: #selector(sendActionAction), for: .touchUpInside) + + view.addSubview(button) + button.snp.makeConstraints { make in + make.height.equalTo(Constraints.sendButton.height) + make.width.equalTo(Constraints.sendButton.width) + make.right.equalToSuperview().inset(Constraints.sendButton.horizontalInset) + make.bottom.equalToSuperview().inset(Constraints.sendButton.bottomInset) + } + + return button + }() + + lazy var balanceLabel: UILabel = { + let label = UILabel( + height: Constraints.balanceLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + label.text = "wallet_balance".localized + label.textColor = UIColor.nynja.manatee + + view.addSubview(label) + + label.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom).offset(Constraints.balanceLabel.topOffset) + make.left.equalToSuperview().offset(Constraints.balanceLabel.horizontalInset) + } + + return label + }() + + lazy var nynBalanceLabel: UILabel = { + let label = UILabel( + height: Constraints.nynBalanceLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.medium.name) + + view.addSubview(label) + + label.snp.makeConstraints { make in + make.top.equalTo(balanceLabel.snp.bottom).offset(Constraints.nynBalanceLabel.topOffset) + make.left.equalToSuperview().offset(Constraints.nynBalanceLabel.leftOffset) + } + + return label + }() + + lazy var usdEquivalentLabel: UILabel = { + let label = UILabel( + height: Constraints.usdEquivalentLabel.height, + color: UIColor.nynja.white, + fontName: FontFamily.NotoSans.regular.name) + + view.addSubview(label) + + label.snp.makeConstraints { make in + make.top.equalTo(nynBalanceLabel.snp.bottom).offset(Constraints.usdEquivalentLabel.topOffset) + make.left.equalToSuperview().offset(Constraints.usdEquivalentLabel.leftOffset) + } + + return label + + }() + + lazy var wordTextField: MaterialTextField = { + + let textField = MaterialTextField() + textField.backgroundColor = UIColor.clear + textField.placeholder = "wallet_nynja_adress".localized + textField.isUserInteractionEnabled = false + textField.rightView = UIImageView(image: UIImage.nynja.Messages.ContextMenu.icCopyContextMenu.image) + textField.rightViewMode = UITextFieldViewMode.always + textField.cursorColor = UIColor.nynja.white + + view.addSubview(textField) + + textField.snp.makeConstraints { make in + make.top.equalTo(usdEquivalentLabel).offset(Constraints.wordTextField.topOffset) + make.height.equalTo(Constraints.wordTextField.height) + make.left.right.equalToSuperview().inset(Constraints.wordTextField.leftInset) + } + + return textField + }() + + private lazy var qrImageView: UIImageView = { + let qrImageView = UIImageView() + + view.addSubview(qrImageView) + + qrImageView.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom).offset(Constraints.qrCodeImageView.topOffset) + make.right.equalToSuperview().inset(Constraints.qrCodeImageView.rightInset) + make.width.height.equalTo(Constraints.qrCodeImageView.sizeSize) + } + + return qrImageView + }() + + + // MARK: Action + @objc private func historyButtonAction() { + presenter.openTransferHistory() + } + + @objc private func sendActionAction() { + presenter.openContacts() + } + + // MARK: - Life Cycle + override func initialize() { + super.initialize() + setupUI() + setupTestingKeysInSubviews() + } + + override func viewDidLoad() { + super.viewDidLoad() + presenter.showed() + } + + // MARK: - UI Setup + private func setupUI() { + + shouldShowSeparator = false + screenTitle = "wallet_details".localized.uppercased() + + navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: shouldShowSeparator, + isVisibleBackButton: true, + title: screenTitle, + navigationHandler: presenter, + backButtonImage: UIImage.nynja.icBackNavigation.image)) + + sendButton.isHidden = false + historyButton.isHidden = false + balanceLabel.isHidden = false + + nynBalanceLabel.text = presenter.formattedCurrencyBalance() + usdEquivalentLabel.text = presenter.formattedUSDEquivalent() + wordTextField.text = presenter.walletAdress() + } + + // MARK: WalletDetailsViewProtocol + func setupQRCodeWith(_ data: Data?) { + let image = data.flatMap { makeQRCodeImage($0) } + qrImageView.image = image + } + + private func makeQRCodeImage(_ data: Data) -> UIImage? { + var qrCode = QRCode(data) + qrCode.color = CIColor(color: UIColor.nynja.white) + qrCode.backgroundColor = CIColor(color: UIColor.nynja.darkLight) + qrCode.size = CGSize( + width: Constraints.qrCodeImageView.sizeSize, + height: Constraints.qrCodeImageView.sizeSize + ) + return qrCode.image + } +} + +// MARK: - Testable +extension WalletDetailsViewController { + + func setupTestingKeys() { + qrImageView.accessibilityIdentifier = "qr_imageView" + historyButton.accessibilityIdentifier = "history_button" + sendButton.accessibilityIdentifier = "send_button" + balanceLabel.accessibilityIdentifier = "balance_label" + nynBalanceLabel.accessibilityIdentifier = "nyn_balance_label" + usdEquivalentLabel.accessibilityIdentifier = "usd_equivalent_label" + wordTextField.accessibilityIdentifier = "word_text_field" + } +} + +extension WalletDetailsViewController: SetInjectable { + struct Dependencies { + let presenter: WalletDetailsPresenterProtocol + } + + func inject(dependencies: WalletDetailsViewController.Dependencies) { + presenter = dependencies.presenter + } +} + +private extension WalletDetailsViewController { + + enum Constraints { + enum qrCodeImageView { + static let topOffset = CGFloat(28.0.adjustedByWidth) + static let sizeSize = CGFloat(100.0.adjustedByWidth) + static let rightInset = CGFloat(16.0.adjustedByWidth) + } + + enum historyButton { + static let borderWidth = CGFloat(2.0) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let height = CGFloat(44.0.adjustedByWidth) + static let width = CGFloat(187.0.adjustedByWidth) + static let bottomInset = CGFloat(28.0.adjustedByWidth) + } + + enum sendButton { + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + static let height = CGFloat(44.0.adjustedByWidth) + static let width = CGFloat(187.0.adjustedByWidth) + static let bottomInset = CGFloat(28.0.adjustedByWidth) + } + + enum balanceLabel { + static let height = CGFloat(22.adjustedByWidth) + static let topOffset = CGFloat(36.0.adjustedByWidth) + static let horizontalInset = CGFloat(16.0.adjustedByWidth) + } + + enum nynBalanceLabel { + static let height = CGFloat(30.adjustedByWidth) + static let topOffset = CGFloat(8.adjustedByWidth) + static let leftOffset = CGFloat(16.adjustedByWidth) + } + + enum usdEquivalentLabel { + static let height = CGFloat(30.adjustedByWidth) + static let topOffset = CGFloat(8.adjustedByWidth) + static let leftOffset = CGFloat(16.adjustedByWidth) + } + + enum wordTextField { + static let height = CGFloat(65.adjustedByWidth) + static let topOffset = CGFloat(36.adjustedByWidth) + static let leftInset = CGFloat(16.adjustedByWidth) + } + } +} diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/WalletDetailsProtocols.swift b/Nynja/Modules/Wallet Flows/WalletDetails/WalletDetailsProtocols.swift new file mode 100644 index 000000000..0c9b0666f --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/WalletDetailsProtocols.swift @@ -0,0 +1,42 @@ +// +// WalletDetailsProtocols.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol WalletDetailsWireFrameProtocol: class { + + func presentWalletDetails(navigation: UINavigationController, for profile: Profile, input: WalletDetailsViewInput) + func close() + func openTransferHistory(for profile: Profile) + func openContacts() +} + +protocol WalletDetailsViewProtocol: class { + func setupQRCodeWith(_ data: Data?) +} + +protocol WalletDetailsPresenterProtocol: BasePresenterProtocol, NavigationProtocol { + func showed() + func openTransferHistory() + func openContacts() + func formattedCurrencyBalance() -> String? + func formattedUSDEquivalent() -> String? + func walletAdress() -> String +} + +protocol WalletDetailsInteractorOutputProtocol: class { +} + +protocol WalletDetailsInteractorInputProtocol: class { + + func fetchQRCodeImageData(completion: ((Data?) -> Void)) + func getProfile() -> Profile + func getNynjaCoinBalance() -> NYNMoney + func getUSDEquivalent() -> Money + func getWalletAdress() -> String +} diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift b/Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift new file mode 100644 index 000000000..26795194e --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift @@ -0,0 +1,57 @@ +// +// WalletDetailsWireFrame.swift +// Nynja +// +// Created by Aleksander Pavliuk on 7/19/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class WalletDetailsWireFrame: WalletDetailsWireFrameProtocol { + + private weak var navigationController: UINavigationController? + + func presentWalletDetails(navigation: UINavigationController, for profile: Profile, input: WalletDetailsViewInput) { + navigationController = navigation + + let view = WalletDetailsViewController() + let presenter = WalletDetailsPresenter() + let interactor = WalletDetailsInteractor() + + // Module components + let viewDependencies = WalletDetailsViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + + let presenterDependencies = WalletDetailsPresenter.Dependencies( + view: view, + interactor: interactor, + wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + + let interactorDependencies = WalletDetailsInteractor.Dependencies( + presenter: presenter, + profile: profile, + profileServices: ProfileServices(profile: profile), + input: input) + interactor.inject(dependencies: interactorDependencies) + + navigationController?.pushViewController(view, animated: true) + } + + func close() { + navigationController?.popViewController(animated: true) + } + + func openTransferHistory(for profile: Profile) { + guard let navigationController = navigationController else { return } + TransferHistoryWireFrame().presentTransferHistory(navigation: navigationController, for: profile) + } + + func openContacts() { + guard let navigationController = navigationController else { return } + ContactsWireFrame().presentContactsForCoinsSending(navigation: navigationController) + } +} + + diff --git a/Nynja/Modules/WalletBalances/Interactor/WalletBalancesInteractor.swift b/Nynja/Modules/WalletBalances/Interactor/WalletBalancesInteractor.swift deleted file mode 100644 index 3ee3ecc23..000000000 --- a/Nynja/Modules/WalletBalances/Interactor/WalletBalancesInteractor.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// WalletBalancesInteractor.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/24/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import Foundation - -final class WalletBalancesInteractor: WalletBalancesInteractorInputProtocol, SetInjectable { - - struct Model { - let nynjaCoint: NYNMoney - let bitCoint: Money - let eos: Money - } - - weak var presenter: WalletBalancesInteractorOutputProtocol! - private var profile: Profile? - - //MARK: WalletBalancesInteractorInputProtocol - func setUpView() { - - guard let profile = self.profile else { - assertionFailure("Profile can't be nil") - return - } - - guard let _ = ProfileServices(profile: profile).wallet else { - assertionFailure("Wallet service can't be nil") - return - } - - presenter.setup(with: .init(nynjaCoint: profile.nynBalance, bitCoint: .init(150), eos: .init(22))) - } -} - -extension WalletBalancesInteractor { - struct Dependencies { - let presenter: WalletBalancesInteractorOutputProtocol - let profile: Profile? - } - - func inject(dependencies: WalletBalancesInteractor.Dependencies) { - presenter = dependencies.presenter - profile = dependencies.profile - } -} - diff --git a/Nynja/Modules/WalletBalances/Presenter/WalletBalancesPresenter.swift b/Nynja/Modules/WalletBalances/Presenter/WalletBalancesPresenter.swift deleted file mode 100644 index 3a59b4290..000000000 --- a/Nynja/Modules/WalletBalances/Presenter/WalletBalancesPresenter.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// WalletBalancesPresenter.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/24/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import Foundation - -final class WalletBalancesPresenter: BasePresenter, WalletBalancesPresenterProtocol, -WalletBalancesInteractorOutputProtocol, SetInjectable { - - enum ImageNames: String { - case nynjaCoinIcon = "ic_nyn" - case btcIcon = "ic_btc" - case eosIcon = "ic_eos" - } - - // MARK: NavigationProtocol - func back() { - wireFrame.close() - } - - - override var itemsFactory: WCItemsFactory? { - return nil - } - - weak var view: WalletBalancesViewProtocol! - var interactor: WalletBalancesInteractorInputProtocol! - var wireFrame: WalletBalancesWireFrameProtocol! - - - // MARK: - WalletBalancesPresenterProtocol - - func showed() { - interactor.setUpView() - } - - - - func setup(with model: WalletBalancesInteractor.Model) { - - func configureCellBody(with money: Money) -> String { - return money.formattedDecimal ?? money.description - } - - let viewModel = WalletBalancesViewController.Model( - cells: - [.init(titleText: model.nynjaCoint.currency.code, - bodyText: configureCellBody(with: model.nynjaCoint), - imageName: ImageNames.nynjaCoinIcon.rawValue), - .init(titleText: model.bitCoint.currency.code, - bodyText: configureCellBody(with: model.bitCoint), - imageName: ImageNames.btcIcon.rawValue), - .init(titleText: model.eos.currency.code, bodyText: - configureCellBody(with: model.eos), - imageName: ImageNames.eosIcon.rawValue)], - navigationItemHeader: "wallet_header".localized.uppercased()) - - view.setup(with: viewModel) - } -} - -extension WalletBalancesPresenter { - struct Dependencies { - let view: WalletBalancesViewProtocol - let interactor: WalletBalancesInteractorInputProtocol - let wireFrame: WalletBalancesWireFrameProtocol - } - - func inject(dependencies: WalletBalancesPresenter.Dependencies) { - view = dependencies.view - interactor = dependencies.interactor - wireFrame = dependencies.wireFrame - } -} diff --git a/Nynja/Modules/WalletBalances/View/WalletBalancesViewController.swift b/Nynja/Modules/WalletBalances/View/WalletBalancesViewController.swift deleted file mode 100644 index 75e1ce817..000000000 --- a/Nynja/Modules/WalletBalances/View/WalletBalancesViewController.swift +++ /dev/null @@ -1,158 +0,0 @@ -// -// WalletBalancesViewController.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/24/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import UIKit -import SnapKit - -final class WalletBalancesViewController: BaseVC, WalletBalancesViewProtocol, SetInjectable { - - struct Model { - let cells: [WalletBalancesTableViewCellModel] - let navigationItemHeader: String - } - - private struct Constraints { - struct tableView { - static let separatorInset = CGFloat(16.0.adjustedByWidth) - - } - } - - var presenter: WalletBalancesPresenterProtocol! { - didSet { - _presenter = presenter - } - } - - lazy var swipeBackHelper: SwipeBackHelper = { - return SwipeBackHelper(with: self, gestureCompletion: nil) - }() - - // MARK: - Views - - lazy var tableView: UITableView = { - let table = UITableView.default - table.delegate = self - table.dataSource = self - table.backgroundColor = UIColor.nynja.clear - table.separatorStyle = .singleLine - table.separatorColor = UIColor.nynja.backgroundGray - table.tableFooterView = UIView() - table.clipsToBounds = true - table.isScrollEnabled = false - table.separatorInset = UIEdgeInsetsMake(0, - Constraints.tableView.separatorInset, - 0, - Constraints.tableView.separatorInset) - - table.register(WalletBalancesTableViewCell.self, forCellReuseIdentifier: WalletBalancesTableViewCell.cellId) - - self.view.addSubview(table) - table.snp.makeConstraints({ (make) in - make.top.equalTo(navigationView.snp.bottom) - make.left.right.bottom.equalToSuperview() - make.bottom.equalToSuperview() - }) - - return table - }() - - - // MARK: - Life Cycle - - override func initialize() { - super.initialize() - setupUI() - setupTestingKeysInSubviews() - presenter.showed() - } - - - // MARK: - UI Setup - private func setupUI() { - swipeBackHelper.addGesture() - } - - func setup(with model: WalletBalancesViewController.Model) { - self.model = model - - tableView.reloadData() - setupNavigationView() - } - - fileprivate var model: Model = .init(cells: [], navigationItemHeader: "") - - private func setupNavigationView() { - shouldShowSeparator = true - screenTitle = model.navigationItemHeader.localized.uppercased() - - navigationView.configure(config: .init( - isVisibleSeparator: shouldShowSeparator, - isVisibleBackButton: true, - title: screenTitle, - navigationHandler: presenter, - backButtonImage: UIImage.nynja.icBackNavigation.image)) - } -} - -// MARK: - Testable -extension WalletBalancesViewController: TestableViewControllerProtocol { - - private enum Keys: String { - case identifier = "identifier" - } - - func setupTestingKeys() { - - } -} - -extension WalletBalancesViewController { - struct Dependencies { - let presenter: WalletBalancesPresenterProtocol - } - - func inject(dependencies: WalletBalancesViewController.Dependencies) { - presenter = dependencies.presenter - } -} - -extension WalletBalancesViewController: UITableViewDelegate, UITableViewDataSource { - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return model.cells.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cellModel = model.cells[indexPath.row] - - var cellOptional: UITableViewCell? { - let cell = tableView.dequeueReusableCell(withIdentifier: WalletBalancesTableViewCell.cellId, - for: indexPath) as? WalletBalancesTableViewCell - cell?.titleLabel.text = cellModel.titleText - cell?.bodyLabel.text = cellModel.bodyText - cell?.cryptoIconImageView.image = UIImage(named: cellModel.imageName) - cell?.isUserInteractionEnabled = false - return cell - } - - guard let cell = cellOptional else { - assertionFailure("Cell should be constuct") - return UITableViewCell(frame: CGRect.zero) - } - - cell.backgroundColor = UIColor.nynja.clear - cell.selectionStyle = .none - - return cell - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return WalletBalancesTableViewCell.height - } -} diff --git a/Nynja/Modules/WalletBalances/WalletBalancesProtocols.swift b/Nynja/Modules/WalletBalances/WalletBalancesProtocols.swift deleted file mode 100644 index 13136e07f..000000000 --- a/Nynja/Modules/WalletBalances/WalletBalancesProtocols.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// WalletBalancesProtocols.swift -// Nynja -// -// Created by Aleksandr Pavliuk on 6/24/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import UIKit - -protocol WalletBalancesWireFrameProtocol: class { - - func presentWalletBalances(for profile: Profile, navigationController: UINavigationController) - func close() -} - -protocol WalletBalancesViewProtocol: class { - - var presenter: WalletBalancesPresenterProtocol! { get set } - func setup(with model: WalletBalancesViewController.Model) - -} - -protocol WalletBalancesPresenterProtocol: BasePresenterProtocol, NavigationProtocol { - - var view: WalletBalancesViewProtocol! { get set } - var interactor: WalletBalancesInteractorInputProtocol! { get set } - var wireFrame: WalletBalancesWireFrameProtocol! { get set } - - - func showed() -} - -protocol WalletBalancesInteractorOutputProtocol: class { - - func setup(with model: WalletBalancesInteractor.Model) -} - -protocol WalletBalancesInteractorInputProtocol: class { - - func setUpView() - var presenter: WalletBalancesInteractorOutputProtocol! { get set } -} diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_btc.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_transfer.imageset/Contents.json similarity index 82% rename from Nynja/Resources/Assets.xcassets/Wallet/ic_btc.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_transfer.imageset/Contents.json index 9f8d411d2..de1b30c30 100644 --- a/Nynja/Resources/Assets.xcassets/Wallet/ic_btc.imageset/Contents.json +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_transfer.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_btc.pdf" + "filename" : "ic_arrow_down.pdf" } ], "info" : { diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_eos.imageset/ic_eos.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_transfer.imageset/ic_arrow_down.pdf similarity index 81% rename from Nynja/Resources/Assets.xcassets/Wallet/ic_eos.imageset/ic_eos.pdf rename to Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_transfer.imageset/ic_arrow_down.pdf index a9b099477b05cee6361ab0c7486ba751ed3a6b84..84830d76332d5308d7c79a738df421c6e0495995 100644 GIT binary patch delta 660 zcmdldcUFEvKz-;WZ@(iBJTBiYTlE`0?tlArPC}TB0#l=j0&j6Wr~0g#8>RCezbe}K zzM$25!=j8dhJ~w^a~=N9bT(*{;^%_o$O{3F+zzz4xZOHl`E!Dy(ekOguSe`&apTUk zR~?V6R-RYrG1TH^+ciz|jswfgB#*^<5>K~GKC(jP`Qo(`d{^FGTOaf&h_Q8^r-(v} z+{$f}S8_)0&fN5UW3u%X#xBbfhi)G}r(&R7^Cy{kty9gb&ZbRwpG-WQwUqhNs!v;$ zB4h#;&YL}$oAc=Nmd%qp8(+Rm-@N_Yku6vICMpy}+`g84!a?-WgU=KCB5&RM#$vvq z^^Vo~b-BWO&p)r8(f7Te*}tNBzTdsX1LAw)W#3dGc~nD*9$Zj!#raX0}BjwCZ=F90c3|7n;IHUUdU$`Z)BKgo@i)nX_%aBoM>X6 zmTG2fY-VndXqjSaVv(F?ZpURqP>DjUf}I^#aYn7XWyS B?%n_Z delta 515 zcmX>rzfW#LKz;25M=mBq9@p>1UGZr1YgBF}ZQwpOOszh$)?77i&#A59~6K<>HZ8oyg zU8c*N+4J*YtaQaQqAlMVRo;!_jN%*|5`j8oE* zERD^Qj7&_7jSP(~lTs2*ER&Ox&Fr{r2r5yCRj{+;DlSPZDyb++P2&Q3%9Kl0)z#mP F3jqJeqJ#hd diff --git a/Nynja/Resources/Assets.xcassets/ic_arrow_right-1.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_up_transfer.imageset/Contents.json similarity index 80% rename from Nynja/Resources/Assets.xcassets/ic_arrow_right-1.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_up_transfer.imageset/Contents.json index 58ae0c878..aa47f2f79 100644 --- a/Nynja/Resources/Assets.xcassets/ic_arrow_right-1.imageset/Contents.json +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_up_transfer.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "icons_ic_arrow_right.pdf" + "filename" : "ic_arrow_down_up.pdf" } ], "info" : { diff --git a/Nynja/Resources/Assets.xcassets/ic_arrow_right-1.imageset/icons_ic_arrow_right.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_up_transfer.imageset/ic_arrow_down_up.pdf similarity index 80% rename from Nynja/Resources/Assets.xcassets/ic_arrow_right-1.imageset/icons_ic_arrow_right.pdf rename to Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_up_transfer.imageset/ic_arrow_down_up.pdf index acfaa91cdc24eb1a05109ed44f8958f86fc36e8c..c249318367c4d6573cf75c3522ebe9f31a9e0275 100644 GIT binary patch delta 666 zcmbOxe@%WuK)vrI?<{6V9+&UMT(JsW|8&(Y>>7{%Ph}uc@uY3%IgpZZ8<@o0=IZ7=VC6o&pz`VPIflWPmPaY;0^c*_BVV z-o(HVUCsb#7`m9TF*bE(78vS`EHKP7HZe5EP-kKW785{rsIjS$!Q`KOcJaok#%6|T zNoE!nh9*XarYT8@Nk#^ymL`Tk4;dR98Q5{z5LBWNt6*oxRa}x-R8motn#N^pW^B%- Ks_N?R#svVz4(F`^ delta 581 zcmca6KTUo@Kz;0lgMQ40JTC8lix#`B>U?~p>|Bkb|KA1FM73IdJ+8>a{e|*K8Gf&%g+z%Cqn)zf#=A2q}wPKfDBCHI9hRo_1_pHt89vOh{}ay_FIvyq|YC1h&|bacP^)qfvJIoshNT4X5=tv5C_MVB)$F*ZXNGd40dM;8Oyi^W_+40XoFrpD-cjZF-| zVgkqxH8wFdn{3H%7oTKen3k4omSSRQlx&n@kZ6>Wn4FSinPh2Vk!WseVr0io;&-4pD@YVSjQDJg~=>nWqR4n^8cUE(>?P9q<4vT{;>(0 z`Z>EsYCqfh%&_eF@1{LI{$NJb61$dVn{}nvOfGmV&V5!(vG$1U^^MEg!k;C~_vU(+ zzj&Eor;@8rQlh-i}FkAf59az-Rnr_8>2+; zr$;Z{OFF;7kF}h4M$Mue|0?s6eT!y)?sE!tzgd{?U^v-$`TCD#3;fx+426o*3X*nw zu)TAbshmrz`k1NClFWlUt|+Bi7Nr$UP-tYg5?InB8q4;gK#z9|gTPgZxC5a!jbCpw zbex_p&a}xXRo3v~u;XytQE5CYL4WgIa{O>HnPb2cl)XyQGTra zPQ=~#R>|X<+guBruC47{QnPVOUX#Pel`9vYU%T@44>P;|`Hz`W^qBZ|tvxbj|Nb27 zvl91@xA(udS@)jV>%jlNRxex_CJ0xp-kQR@ap%IroG%#Ef@*B1t_fZua`w?8zxvfn z&wOYLm|d%JNz_1hiIrgcTdiW@lJ#v;ueXUz+L=^#V3JxrJA+c*)UAtJ7H!pt_-GTX5yH3RecLy` zHGIc*w8s8U*4W0GyYlVQ{n2G^uTJ$_T(O<~N{k_<|JLmm!Kf<{v!--eDR|kiG;F=k zadK;F)#fd~>+;_fR=c0P@Fz|3jKaz-ZHWv^oR-ZE>|WS8>*$$P4vZ%4nQz+Loey%| z6I+nYCSoV`?!uaFnf8T$A0&i(Sw<-|>fRMw6K~!n`TyFTde6T<++T@18#+%cEbL%d zl)be35$6lS)P3gH>1JiEkX-8&5g_q%neOV%qPbR z*XtNtnwlvXfPg}t0vDKJU|?Zrf-YukZfr1_Lqw(Cz}(UlUDCk95>ta27Il_pM(BEt z&CD^(Gqx}^#878p1`-oQajCJTp~>VuB6jhKsj21$=0<5ICPs;=mga^QCP~H?=1GQT smT4&#rm2Z`Ts8!iD8wq**>M$@Bo>ua6s4wdnHU%wa;d7i`nz!f05YDMG5`Po delta 2083 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 literal 0 HcmV?d00001 diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_eos.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_eth_disabled.imageset/Contents.json similarity index 81% rename from Nynja/Resources/Assets.xcassets/Wallet/ic_eos.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Wallet/ic_eth_disabled.imageset/Contents.json index 8c70e0f7b..539b493ad 100644 --- a/Nynja/Resources/Assets.xcassets/Wallet/ic_eos.imageset/Contents.json +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_eth_disabled.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_eos.pdf" + "filename" : "ic_eth_disabled.pdf" } ], "info" : { diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_eth_disabled.imageset/ic_eth_disabled.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_eth_disabled.imageset/ic_eth_disabled.pdf new file mode 100644 index 0000000000000000000000000000000000000000..59495718c71e656f2a193af02b223725d306a0aa GIT binary patch literal 5699 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 literal 0 HcmV?d00001 diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/Contents.json new file mode 100644 index 000000000..3f809c910 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_wallet_absent.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/ic_wallet_absent.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/ic_wallet_absent.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6d6efa0e5408df6a35b753f1d1309535ae723963 GIT binary patch literal 5444 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 sm9hk~4A;KzpY^VK<{nG6d@72b7hQ8W=z&NHs zICLi04nwPlujMs!=f`&j1?@74_1eoZy|_WZsPIV{8{eCRO-D_(772cTGgo5uZKa@V zzavVG<|uBM<#f#TLR94T^NIJwOsgCVwe~CbWabu`AAd2$X!XDPZQ5d6SDjrJ&El%R zz00+4MwjBYpnrFblIQOFrXm%}E>UFi{(07M=_e0={1a=7!-BIOW%9&Q=Ds0rY=`%UP^IE zQEFl?h?Sp|#bv5spa7zcEX-g+Mw37Ch;ClQD9*!aZfI_30t5z=5Ail?nHVb=fPg}t z0vDKJU|?ckh%RPqY-m0?mrt$U!U$c;z{CPmxe*q1rY4r?dX0_DFw8SHwzR-dXJP^o z<41C*v8jRKWJP|vcoQ=-gVdB%lN1wE%S5xZq$CpqW8>5`(=szfXQbNPTl4PwVslT1H|$mG;NhEm*9#K!Z!`$~*Zpf4#k*p4&Hj4iG$KNPplG zx{N8W$M45Kcdy&ewFAAp!hNR)=^uJ3qtN0bvvY0(v-OM|L*KPAM{KLrkG?y9BYJ@r2b3a`;{$h$zka>OS(M4ff19wFSd?{KT z@gytQ@W`~&xAiVxTj{NSafQ`^35zS^!zW)cclI~Cr@ZiEe(mlR*Euzz3p0Y_xX5;Lcb7J}-Ynguxv)-n^nViEY!)Umo@0y7K@j4jR3#f*&&jV2fIsnwgCqe~f>m}4q8 z!lKU9!~|Whv5_f;dB(<;#u(~MOdw+XNbWQ?u{4{k#%~v&XqjqkX=Iw1mSSOMl4zKg zY;K;IXkw9KX_;)1lxA#h$7MrMi9)P`ogG(kNn%k+MNw)Rm$8|lF_)^UtG^o;02l4j AzW@LL diff --git a/Nynja/Resources/Constants.swift b/Nynja/Resources/Constants.swift index 275753bc4..766e28ddf 100644 --- a/Nynja/Resources/Constants.swift +++ b/Nynja/Resources/Constants.swift @@ -39,11 +39,11 @@ struct Constants { static let history = "history_title"; static let selectCountry = "select_country_title"; static let dataAndStorage = "data_and_storage" - + static let search = "Search"; static let save = "save" - static let wrongProtocol = "wrongVersion" + static let transferHistory = "transfer_history_title"; } struct Size { diff --git a/Nynja/Resources/en.lproj/Localizable.strings b/Nynja/Resources/en.lproj/Localizable.strings index 91ad8e088..c859f9571 100644 --- a/Nynja/Resources/en.lproj/Localizable.strings +++ b/Nynja/Resources/en.lproj/Localizable.strings @@ -270,6 +270,7 @@ "login_to_Nynja"="Log in to Nynja"; "open_and_login_to_share"="Open Nynja and log in to share"; "next"="NEXT"; +"previous" = "previous"; // MARK: settings group "group_options"="GROUP OPTIONS"; @@ -585,7 +586,6 @@ "main_undefined"="Undefined"; // MARK: Profile -"profile_balance"="Balance"; "star_added"="Added:"; "profile_contact_cell_added"="Added"; "action_cell_profile_starred_messages"="Starred Messages"; @@ -822,17 +822,6 @@ "incoming_call" = "Incoming Call"; "End_conference_call_failed"="End conference call failed"; -//MARK: Wallet -"wallet_recepient" = "Recepient"; -"wallet_balance" = "Currency Balance"; -"wallet_amount" = "Amount"; -"wallet_alert" = "Your balance has not enough funds to transfer this amount"; -"wallet_send" = "send coins"; -"wallet_header" = "Wallet"; -"wallet_created" = "Wallet has been created"; -"wallet_add_title" = "Add\nWallet"; -"wallet_transfer_deletion_info" = "The message with Transfer Information will be deleted only for you in chat history, but this message will be still available in Transfer History"; - //MARK: Marketplace //**Submenu titles < CircleMenuItemType case naming are constrained to this values > @@ -880,3 +869,51 @@ "interpretation_language_not_selected" = "Please, choose the language you want to interpret from."; "interpretation_languages_are_equal" = "Please, choose different languages."; + +//MARK: Wallet +"wallet_recipient" = "Recipient"; +"wallet_balance" = "Currency Balance"; +"wallet_amount" = "Amount"; +"wallet_alert" = "Your balance has not enough funds to transfer this amount"; +"wallet_send" = "send coins"; +"wallet_header" = "Wallet"; +"wallet_created" = "Wallet has been created"; +"wallet_add_title" = "Add\nWallet"; +"wallet_transfer_deletion_info" = "The message with Transfer Information will be deleted only for you in chat history, but this message will be still available in Transfer History"; +"wallet_notes" = "Notes to the recipient"; +"wallet_coming_soon" = "Coming soon"; +"wallet_dont_have_any" = "You don't have any wallet. Please create your first wallet now."; +"wallet_create" = "create a wallet"; +"wallet_name" = "Wallet name"; +"wallet_enter_pin" = "Enter pin code (6 digits)"; +"wallet_confirm_pin" = "Confirm pin code"; +"wallet_pin_require" = "This pin code will be required when sending coins from your wallets."; +"wallet_seed_backup" = "seed backup"; +"wallet_seed_description" = "The following 12 words are used to recover your wallet. Write them down correctly on piece of paper and keep them safe.\nWe do not store your words and if you cannot remember them, you will not be able to access to your wallet in the future.\nAfter we show you the 12 words, we will request you to re-enter them to confirm you wrote them down.\nRemember: Whoever has access to your 12 words, has access to your wallet."; +"wallet_start_verification" = "start verification"; +"wallet_seed_verification" = "seed verification"; +"wallet_seed_verification_description" = "Please enter the words you just wrote down, one after another, to make sure that everything is backed up correctly."; +"wallet_enter_word" = "Enter word"; +"wallet_verification_success_description" = "You have successfully created your wallet. Don’t forget to store your 12 words safely."; +"wallet_verification_success" = "Success"; +"wallet_details" = "wallet details"; +"wallet_send" = "send"; +"wallet_balance" = "Balance"; +"wallet_nynja_adress" = "Your NYNJAcoin Address:"; +"wallet_empty_history" = "You have no transaction history yet. You can send coins to other users with a chat or directly to any address from within the wallet."; +"wallet_transfers" = "Transfers"; +"wallet_show_in_blockchain" = "Show in blockchain"; +"wallet_transfer_details" = "transfer details"; +"wallet_transfer_confirmations" = "Confirmations"; +"wallet_view_on_blockchain" = "View on blockchain explorer:"; +"wallet_notes_to_recipient" = "Notes to the recipient"; +"wallet_external_address" = "External address"; +"wallet_name_length" = "The length of the name must be between %@ and %@"; +"wallet_passcode_length" = "Pin code length must be between equal to %@"; +"wallet_passcode_match" = "Pin code doesn't math"; +"wallet_word" = "Word"; + +//MARK: Passcode +"enter_passcode" = "Enter passcode"; +//MARK: Transfer history +"transfer_history_title" = "Transfer History"; diff --git a/Nynja/Resources/ru.lproj/Localizable.strings b/Nynja/Resources/ru.lproj/Localizable.strings index ec8f4d2a3..576c17d9c 100644 --- a/Nynja/Resources/ru.lproj/Localizable.strings +++ b/Nynja/Resources/ru.lproj/Localizable.strings @@ -258,6 +258,7 @@ "login_to_Nynja"="Войти в Nynja"; "open_and_login_to_share"="Откройте Nynja и войдите чтоб поделиться"; "next"="Следующий"; +"previous" = "педыдущий"; // MARK: settings group "group_options"="Опции группы"; @@ -543,7 +544,6 @@ "main_undefined"="Неопределенный"; // MARK: Profile -"profile_balance"="Баланс"; "star_added"="Добавлен:"; "profile_contact_cell_added"="Добавлен"; "action_cell_profile_starred_messages"="Сообщение со звездочкой"; @@ -715,17 +715,6 @@ "outgoing_call" = "Исходящий звонок"; "incoming_call" = "Входящий звонок"; -//MARK: Wallet -"wallet_recepient" = "Получатель"; -"wallet_balance" = "Баланс"; -"wallet_amount" = "Сумма"; -"wallet_alert" = "На балансе недостаточно средств для осуществления транзакции"; -"wallet_send" = "Отправить средства"; -"wallet_header" = "Кошелек"; -"wallet_created" = "Кошелек успешно создан"; -"wallet_add_title" = "Создать\nКошелек"; -"wallet_transfer_deletion_info" = "Сообщение с информацей о трансфере будет удалено только для вас в истории чата, но это сообщение будет по-прежнему доступно в истории передачи"; - // MARK: Data and Storage "data_and_storage" = "Хранилище"; "automatic_media_download" = "Автоматическая загрузка данных"; @@ -748,6 +737,7 @@ "music" = "Music"; "gifs" = "GIFs"; +<<<<<<< HEAD //MARK: Marketplace //**Submenu titles < CircleMenuItemType case naming are constrained to this values > @@ -795,3 +785,51 @@ "interpretation_language_not_selected" = "Please, choose the language you want to interpret from."; "interpretation_languages_are_equal" = "Please, choose different languages."; + +//MARK: Wallet +"wallet_transfer_deletion_info" = "Сообщение с информацей о трансфере будет удалено только для вас в истории чата, но это сообщение будет по-прежнему доступно в истории передачи"; +"wallet_recepient" = "Получатель"; +"wallet_balance" = "Баланс"; +"wallet_amount" = "Сумма"; +"wallet_alert" = "На балансе недостаточно средств для осуществления транзакции"; +"wallet_send" = "Отправить средства"; +"wallet_header" = "Кошелек"; +"wallet_created" = "Кошелек успешно создан"; +"wallet_notes" = "Примечания"; +"wallet_coming_soon" = "Скоро"; +"wallet_dont_have_any" = "У вас нет кошелька. Создайте свой первый кошелек прямо сейчас."; +"wallet_create" = "создать кошелек"; +"wallet_name" = "Имя кошелька"; +"wallet_enter_pin" = "Введите пин код"; +"wallet_confirm_pin" = "Повторите пин код"; +"wallet_pin_require" = "Этот пин код будет необходим для перевода валюты с ваших кошельков"; +"wallet_seed_backup" = "резервное копирование"; +"wallet_seed_description" = "Следующие 12 слов используются для восстанавления кошелька. Запишите их правильно на листик бумаги и сохраните. Мы не храним ваши слова и если вы не можете их запомнить, вы не получите доступ к вашему кошельку в будующем. После того как мы покажем 12 слов, мы попросим вас ввести их для подтверждения. Помните: кто имеет доступ к вашим 12ти словам, имеет доступ к вашему кошельку"; +"wallet_start_verification" = "начать проверку"; +"wallet_seed_verification" = "проверка слов"; +"wallet_seed_verification_description" = "Пожалуйста введите слова которые вы записали одно за другим, что бы убедиться что все скопировано правильно."; +"wallet_enter_word" = "Введите слово"; +"wallet_verification_success_description" = "Кошелек успешно создан. Не забудьте сохранить фразу из 12 слов в надежном месте"; +"wallet_verification_success" = "Успешно"; +"wallet_details" = "кошелек"; +"wallet_send" = "послать"; +"wallet_balance" = "Баланс"; +"wallet_nynja_adress" = "Ваш NYNJAcoin Адресс:"; +"wallet_empty_history" = "У вас еще нет истории транзакций. Вы можете отправлять монеты другим пользователям с помощью чата или напрямую на любой адрес из кошелька."; +"wallet_transfers" = "Трансферы"; +"wallet_show_in_blockchain" = "Показать в цепочке блоков"; +"wallet_transfer_details" = "детали трансфера"; +"wallet_transfer_confirmations" = "Подтверждения"; +"wallet_view_on_blockchain" = "Посмотреть в цепочке блоков:"; +"wallet_notes_to_recipient" = "Примечания к получателю"; +"wallet_external_address" = "Внешний адрес"; +"wallet_name_length" = "Имя кошелька должно быть больше %@ и не более %@ знаков"; +"wallet_passcode_length" = "Длинна пароля должна равняться %@ символам"; +"wallet_passcode_match" = "Пароли не совпадают"; +"wallet_word" = "Слово"; + +//MARK: Passcode +"enter_passcode" = "Введите код безопасности"; +//MARK: Transfer history +"transfer_history_title" = "История переводов"; +>>>>>>> [NY-1597] Updated wallet related UI (#993) (#983) (#979) (#973) (#974) (#1025) diff --git a/Nynja/ServerModel/ModelAdapters/ContactServices.swift b/Nynja/ServerModel/ModelAdapters/ContactServices.swift index 9fdddc8c6..809b4f18a 100644 --- a/Nynja/ServerModel/ModelAdapters/ContactServices.swift +++ b/Nynja/ServerModel/ModelAdapters/ContactServices.swift @@ -8,23 +8,21 @@ protocol ContactServicesProtocol { var wallet: Service? { get } - var vox: Service? { get } } -class ContactServices: ContactServicesProtocol { - private let contact: Contact - +class ContactServices: ServicesDecorator, ContactServicesProtocol { + + let contact: Contact + init(contact: Contact) { self.contact = contact } - + var wallet: Service? { - return contact.services?.first { ($0.type as? StringAtom)?.string == "wallet" } - } - - var vox: Service? { - return contact.services?.first { ($0.type as? StringAtom)?.string == "vox" } + guard let services = contact.services else { + return nil + } + return services.first(where: isWalletType) + } } - - diff --git a/Nynja/ServerModel/ModelAdapters/ProfileServices.swift b/Nynja/ServerModel/ModelAdapters/ProfileServices.swift index f82ec66e8..57cb974e7 100644 --- a/Nynja/ServerModel/ModelAdapters/ProfileServices.swift +++ b/Nynja/ServerModel/ModelAdapters/ProfileServices.swift @@ -7,23 +7,40 @@ // protocol ProfileServicesProtocol { - var vox: Service? { get } var wallet: Service? { get } } -class ProfileServices: ProfileServicesProtocol { - private let profile: Profile +class ProfileServices: ServicesDecorator, ProfileServicesProtocol { + let profile: Profile + init(profile: Profile) { self.profile = profile } + + var wallet: Service? { + guard let services = profile.services else { + return nil + } + return services.first(where: isWalletType) + } +} + +class ServicesDecorator { - var vox: Service? { - return profile.services?.first{ ($0.type as? StringAtom)?.string == "vox" } + func isWalletType(service: Service) -> Bool { + return isService(service, hasType: "wallet") } - var wallet: Service? { - return profile.services?.first { ($0.type as? StringAtom)?.string == "wallet" } + private func stringAtom(_ object: AnyObject) -> StringAtom? { + return object as? StringAtom + } + + private func isService(_ service: Service, hasType type: String) -> Bool { + guard let typeObject = service.type, let typeAtom = stringAtom(typeObject) else { + return false + } + return typeAtom.string == type } } diff --git a/Nynja/Services/MQTT/MQTTServiceWallet.swift b/Nynja/Services/MQTT/MQTTServiceWallet.swift index 00565d312..fefee8208 100644 --- a/Nynja/Services/MQTT/MQTTServiceWallet.swift +++ b/Nynja/Services/MQTT/MQTTServiceWallet.swift @@ -8,12 +8,13 @@ extension MQTTService { - func addWalletToProfile(walletAddress: String, balance: NYNMoney) { + func addWalletToProfile(walletAddress: String, balance: NYNMoney, password: String) { guard let phoneId = StorageService.sharedInstance.phone else { return } let input = WalletMQTTModel.RequestInput(phoneId: phoneId, + password: password, action: .addToProfile, walletAddress: walletAddress, walletBalance: balance) @@ -21,12 +22,13 @@ extension MQTTService { publish(model: model) } - func changeProfileBalance(walletAddress: String, balance: NYNMoney) { + func changeProfileBalance(walletAddress: String, balance: NYNMoney, password: String) { guard let phoneId = StorageService.sharedInstance.phone else { return } let input = WalletMQTTModel.RequestInput(phoneId: phoneId, + password: password, action: .changeBalance, walletAddress: walletAddress, walletBalance: balance) diff --git a/Nynja/Services/Models/SendModel.swift b/Nynja/Services/Models/SendModel.swift index 9b4d8929a..14e2ea545 100644 --- a/Nynja/Services/Models/SendModel.swift +++ b/Nynja/Services/Models/SendModel.swift @@ -25,9 +25,9 @@ enum SendMessageType: String { case contact = "contact" case reply = "reply" case translate = "translate" - case autotranslate = "posttranslate" + case autotranslate = "posttranslate" case transcribe = "transcribe" - case payment = "payment" + case transfer = "transfer" var messageDescription: String { switch self { diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index a790935d3..762eb86ae 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -29,10 +29,12 @@ protocol ServiceFactoryProtocol: class { func makeStickersProvider() -> StickersProviding func makeTextInputValidationService() -> TextInputValidationServiceProtocol + func makeWalletCreationTextInputValidationService() -> WalletCreationTextInputValidationServiceProtocol + func makeWalletOpeningTextInputValidationService() -> WalletOpeningTextInputValidationServiceProtocol + func makeWalletFundingNetworkService() -> WalletFundingNetworkService func makePermissionManager() -> PermissionManager - + func makeWalletService() -> WalletService - func makeSyncFileManager() -> SyncFileManager func makeAmazonManager() -> AmazonManager @@ -125,8 +127,23 @@ final class ServiceFactory: ServiceFactoryProtocol { } func makeWalletService() -> WalletService { - let dep = WalletService.Dependencies() - return .init(dependencies: dep) + let dep = WalletService.Dependencies( + mqttService: makeMQTTService(), + walletFundingNetworkService: makeWalletFundingNetworkService()) + return WalletService(dependencies: dep) + } + + func makeWalletCreationTextInputValidationService() -> WalletCreationTextInputValidationServiceProtocol { + return TextInputValidationService() + } + + func makeWalletOpeningTextInputValidationService() -> WalletOpeningTextInputValidationServiceProtocol { + return TextInputValidationService() + } + + func makeWalletFundingNetworkService() -> WalletFundingNetworkService { + let dependencies = WalletFundingNetworkService.Dependencies(client: URLSessionNetworkClient()) + return WalletFundingNetworkService(dependencies: dependencies) } func makeSyncFileManager() -> SyncFileManager { diff --git a/Nynja/Services/StorageService.swift b/Nynja/Services/StorageService.swift index 8870942b7..9ef71ae31 100644 --- a/Nynja/Services/StorageService.swift +++ b/Nynja/Services/StorageService.swift @@ -13,7 +13,7 @@ import CryptoSwift //MARK: - The Game is began class StorageService { - private let passphraseKey = "com.nynja.mobile.communicator.storage.service.passphrase" + private let passphraseKey = KeychainService.Keys.dataBasePassphrase let userDefaults = UserDefaults(suiteName: Bundle.main.appGroupName) let keychain = KeychainService.standard diff --git a/Nynja/Services/WalletService/Entities/WalletServiceResult.swift b/Nynja/Services/WalletService/Entities/WalletServiceResult.swift new file mode 100644 index 000000000..53b86c282 --- /dev/null +++ b/Nynja/Services/WalletService/Entities/WalletServiceResult.swift @@ -0,0 +1,21 @@ +// +// WalletServiceResult.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/23/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +enum WalletServiceResult { + case success(T) + case failure(Error) + + var error: Error? { + switch self { + case .success: + return nil + case .failure(let error): + return error + } + } +} diff --git a/Nynja/Services/WalletService/Entities/WalletServiceWallet.swift b/Nynja/Services/WalletService/Entities/WalletServiceWallet.swift new file mode 100644 index 000000000..45633ffb4 --- /dev/null +++ b/Nynja/Services/WalletService/Entities/WalletServiceWallet.swift @@ -0,0 +1,17 @@ +// +// WalletServiceWallet.swift +// Nynja +// +// Created by Aleksander Pavliuk on 8/23/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct WalletServiceAccount { + let balance: String + let address: String +} + +struct WalletServiceWallet { + let name: String? + let account: WalletServiceAccount +} diff --git a/Nynja/Services/WalletService/WalletService.swift b/Nynja/Services/WalletService/WalletService.swift index 3523fdc72..4dcc642cb 100644 --- a/Nynja/Services/WalletService/WalletService.swift +++ b/Nynja/Services/WalletService/WalletService.swift @@ -6,52 +6,136 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // +import NynjaSDK.NynjaWallet + protocol WalletServiceProtocol { - func createWallet(phoneId: String, completion: @escaping (Error?) -> Void) - func sendCoins(amount: NYNMoney, - recepientСontact: Contact, - senderProfile: Profile, - completion: @escaping (Error?) -> Void) + func makeNynjaWallet(seeds: [String], + name: String, + passcode: String, + completion: (WalletServiceResult) -> ()) + func makeMnemonics(completion: (WalletServiceResult<[String]>) -> ()) + func getWallet(name: String?, + passcode: String, + completion: @escaping (WalletServiceResult) -> ()) + func makeTransfer(name: String?, + passcode: String, + receipientAddress: String, + tockenType: String?, + amount: String, + completion: @escaping (WalletServiceResult) -> ()) } -final class WalletService: InitializeInjectable { +final class WalletService: InitializeInjectable, WalletServiceProtocol { + + private var mqttService: MQTTService! + private var walletFundingNetworkService: WalletFundingNetworkService! struct Dependencies { + let mqttService: MQTTService + let walletFundingNetworkService: WalletFundingNetworkService } init(dependencies: WalletService.Dependencies) { + mqttService = dependencies.mqttService + walletFundingNetworkService = dependencies.walletFundingNetworkService } enum WalletServiceError: LocalizedError { - case walletAddressCantBeNil - case walletBalanceCantBeNil - case fromWalletNetworkClient(String) - - var localizedDescription: String { - switch self { - case .walletAddressCantBeNil: - return "Wallet addres can't be nil" - case .walletBalanceCantBeNil: - return "Wallet balance can't be nil" - case .fromWalletNetworkClient(let desc): - return desc - } - } + case walletNameNotSaved + case accountNotFound + case walletCantGetName } } +//MARK: WalletServiceProtocol +extension WalletService { + + func makeNynjaWallet(seeds: [String], + name: String, + passcode: String, + completion: (WalletServiceResult) -> ()) { + do { + let account = try makeWalletWithSingleAccount(seeds: seeds, name: name, passcode: passcode) + let address = try account.getAddress() + walletFundingNetworkService.fund(address: address, completion: nil) + + mqttService.addWalletToProfile(walletAddress: address, balance: NYNMoney(0), password: name) -extension WalletService: WalletServiceProtocol { + completion(.success(name)) + } catch { + completion(.failure(error)) + } + } + + func makeMnemonics(completion: (WalletServiceResult<[String]>) -> ()) { + do { + let seeds = try NynjaWallet.createMnemonics(NynjaSDK.ENG) + completion(.success(seeds)) + } catch { + completion(.failure(error)) + } + } - func sendCoins(amount: NYNMoney, - recepientСontact: Contact, - senderProfile: Profile, - completion: @escaping (Error?) -> Void) { + func getWallet(name: String?, passcode: String, completion: @escaping (WalletServiceResult) -> ()) { + do { + let account = try getAccountOfWallet(with: name) + let address = try account.getAddress() + try account.getBalance(nil, withPassphrase: passcode) { (balance, error) in + let wallet = WalletServiceWallet( + name: name, + account: WalletServiceAccount( + balance: balance, + address: address + ) + ) + completion(.success(wallet)) + } + } catch { + completion(.failure(error)) + } + } + + func makeTransfer(name: String?, + passcode: String, + receipientAddress: String, + tockenType: String? = nil, + amount: String, + completion: @escaping (WalletServiceResult) -> ()) { + do { + let account = try getAccountOfWallet(with: name) + try account.sendCoins( + receipientAddress, + withTokenType: tockenType, + withPassphrase: passcode, + withAmount: amount) { (transactionId, error) in + completion(.success(transactionId)) + } + } catch { + completion(.failure(error)) + } + } +} +private extension WalletService { + func makeWalletWithSingleAccount(seeds: [String], name: String, passcode: String) throws -> NynjaAccount { + let wallet = try NynjaWallet.create(seeds, with: NynjaSDK.ENG, withName: name, withPassphrase: passcode) + let account = try wallet.createAccount(name, withPassphrase: passcode, ofCoinType: NynjaSDK.ETH) + return account + } + + func firstAccount(from accounts: [NynjaAccount]) throws -> NynjaAccount { + guard let account = accounts.first else { + throw WalletServiceError.accountNotFound + } + return account + } - completion(nil) + func getAccountOfWallet(with name: String?) throws -> NynjaAccount { + let wallet = try NynjaWallet.open(name) + return try getFirstAccount(of: wallet) } - func createWallet(phoneId: String, completion: @escaping (Error?) -> Void) { - completion(nil) + func getFirstAccount(of wallet: NynjaWallet) throws -> NynjaAccount { + let accounts = try wallet.getAccounts() + return try firstAccount(from: accounts) } } diff --git a/Nynja/Utils/Money/CryptoCurrency.swift b/Nynja/Utils/Money/CryptoCurrency.swift index 4765efb6c..2530e66d8 100644 --- a/Nynja/Utils/Money/CryptoCurrency.swift +++ b/Nynja/Utils/Money/CryptoCurrency.swift @@ -10,47 +10,47 @@ import Foundation protocol CryptoCurrency: CurrencyType {} -/// Bitcoin -enum BTC: CryptoCurrency { +/// Nynja coin +enum NYN: CryptoCurrency { public static var code: String { - return "BTC" + return "NYN" } public static var name: String { - return "Bitcoin" + return "NYNJ" } public static var minorUnit: Int { - return 8 + return 2 } } -/// Nynja coin -enum NYN: CryptoCurrency { +/// Ethereum +enum ETH: CryptoCurrency { public static var code: String { - return "NYN" + return "ETH" } public static var name: String { - return "NYNJ" + return "Ethereum" } public static var minorUnit: Int { - return 2 + return 18 } } -/// EOS -enum EOS: CryptoCurrency { +/// Bitcoin +enum BTC: CryptoCurrency { public static var code: String { - return "EOS" + return "BTC" } public static var name: String { - return "EOS.IO" + return "Bitcoin" } public static var minorUnit: Int { - return 0 + return 8 } } diff --git a/Nynja/Validation/TextInputValidationService.swift b/Nynja/Validation/TextInputValidationService.swift index c9f090947..cabfaa064 100644 --- a/Nynja/Validation/TextInputValidationService.swift +++ b/Nynja/Validation/TextInputValidationService.swift @@ -15,10 +15,56 @@ protocol TextInputValidationServiceProtocol { func validateInterpretationTime(_ number: Int) throws -> Int } -final class TextInputValidationService: TextInputValidationServiceProtocol { - - //MARK: - Public Validation methods - +protocol WalletCreationTextInputValidationServiceProtocol { + func validateWallet(name: String?) -> TextInputValidationResult + func validateWallet(passcode: String?) -> TextInputValidationResult + func validateWallet(passcode: String, confirmPasscode: String) -> TextInputValidationResult +} + +protocol WalletOpeningTextInputValidationServiceProtocol { + func validateWallet(passcode: String) -> TextInputValidationResult +} + +enum TextInputValidationResult { + case success + case failure(Error) +} + +enum TextInputValidationError: LocalizedError { + + case firstNameIsEmpty + case firstNameHasNotEnoughSymbols + case firstNameHasTooMuchSymbols + case lastNameHasTooMuchSymbols + case groupNameIsEmpty + case interpretationTimeIsOutOfRange + + var errorDescription: String? { + switch self { + case .firstNameIsEmpty: + return "nameField_empty".localized + case .firstNameHasNotEnoughSymbols: + return "First_Name_must_be_at_least".localized + case .firstNameHasTooMuchSymbols: + return "First_Name_must_be_at_more".localized + case .lastNameHasTooMuchSymbols: + return "Last_Name_must_be_at_more".localized + case .groupNameIsEmpty: + return "Group_Name_Empty_Message".localized + case .interpretationTimeIsOutOfRange: + return "interpretation_time_out_of_range".localized + } + } +} + +final class TextInputValidationService: TextInputValidationServiceProtocol, +WalletCreationTextInputValidationServiceProtocol, WalletOpeningTextInputValidationServiceProtocol{ + +} + +//MARK: TextInputValidationServiceProtocol +extension TextInputValidationService { + func validateFirstName(_ name: String?) throws -> String { guard let text = name, !text.isEmpty else { throw TextInputValidationError.firstNameIsEmpty @@ -32,7 +78,7 @@ final class TextInputValidationService: TextInputValidationServiceProtocol { } return text } - + func validateLastNameNew(_ name: String?) throws -> String { guard let text = name else { return "" } if text.count > 32 { @@ -56,29 +102,85 @@ final class TextInputValidationService: TextInputValidationServiceProtocol { } } -enum TextInputValidationError: LocalizedError { - - case firstNameIsEmpty - case firstNameHasNotEnoughSymbols - case firstNameHasTooMuchSymbols - case lastNameHasTooMuchSymbols - case groupNameIsEmpty - case interpretationTimeIsOutOfRange - - var errorDescription: String? { - switch self { - case .firstNameIsEmpty: - return "nameField_empty".localized - case .firstNameHasNotEnoughSymbols: - return "First_Name_must_be_at_least".localized - case .firstNameHasTooMuchSymbols: - return "First_Name_must_be_at_more".localized - case .lastNameHasTooMuchSymbols: - return "Last_Name_must_be_at_more".localized - case .groupNameIsEmpty: - return "Group_Name_Empty_Message".localized - case .interpretationTimeIsOutOfRange: - return "interpretation_time_out_of_range".localized +//MARK: WalletCreationTextInputValidationServiceProtocol +extension TextInputValidationService { + + private enum Consts { + enum Wallet { + static let NameLenthMin = 2 + static let NameLenthMax = 32 + static let PasscodeLenth = 6 + } + } + + enum WalletCreationInputValidationError: LocalizedError { + + case nameNotInRange + case passcodeNotInSize + case passcodesNotMatch + + var errorDescription: String? { + switch self { + case .nameNotInRange: + return nameLenghtFailDescription + case .passcodeNotInSize: + return passcodeLenghtFailDescription + case .passcodesNotMatch: + return "wallet_passcode_match".localized + } + } + + private var passcodeLenghtFailDescription: String { + return String( + format: "wallet_passcode_length".localized, + String(Consts.Wallet.PasscodeLenth)) + } + + private var nameLenghtFailDescription: String { + return String( + format: "wallet_name_length".localized, + String(Consts.Wallet.NameLenthMin), + String(Consts.Wallet.NameLenthMax)) + } + } + + func validateWallet(passcode: String?) -> TextInputValidationResult { + if passcode?.count != Consts.Wallet.PasscodeLenth { + return .failure(WalletCreationInputValidationError.passcodeNotInSize) + } + return .success + } + + func validateWallet(name: String?) -> TextInputValidationResult { + guard let name = name else { + return .failure(WalletCreationInputValidationError.nameNotInRange) + } + + if Consts.Wallet.NameLenthMin...Consts.Wallet.NameLenthMax ~= name.count { + return .success + } + return .failure(WalletCreationInputValidationError.nameNotInRange) + } + + func validateWallet(passcode: String, confirmPasscode: String) -> TextInputValidationResult { + if passcode != confirmPasscode { + return .failure(WalletCreationInputValidationError.passcodesNotMatch) + } + return .success + } +} + +// MARK: WalletOpeningTextInputValidationServiceProtocol +extension TextInputValidationService { + + enum WalletOpeningInputValidationError: Error { + case passcodeNotInRange + } + + func validateWallet(passcode: String) -> TextInputValidationResult { + if passcode.count > Consts.Wallet.PasscodeLenth { + return .failure(WalletOpeningInputValidationError.passcodeNotInRange) } + return .success } } diff --git a/Nynja/WalletFundingNetworkService/Entities/WalletFundingNetworkResponse.swift b/Nynja/WalletFundingNetworkService/Entities/WalletFundingNetworkResponse.swift new file mode 100644 index 000000000..1782f2572 --- /dev/null +++ b/Nynja/WalletFundingNetworkService/Entities/WalletFundingNetworkResponse.swift @@ -0,0 +1,12 @@ +// +// WalletFundingNetworkResponse.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 8/31/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +struct WalletFundingNetworkResponse: Decodable { + var hash: String + var value: String +} diff --git a/Nynja/WalletFundingNetworkService/WalletFundingNetworkRouter.swift b/Nynja/WalletFundingNetworkService/WalletFundingNetworkRouter.swift new file mode 100644 index 000000000..1c1f8f651 --- /dev/null +++ b/Nynja/WalletFundingNetworkService/WalletFundingNetworkRouter.swift @@ -0,0 +1,52 @@ +// +// WalletFundingNetworkRouter.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 8/31/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +enum WalletFundingNetworkRouter: NetworkRouter { + case mint(queryItems: HTTPQueryItems) + + static var scheme: String { + return "http" + } + + static var port: Int? { + return nil + } + + static var host: String { + return "https://wallet.dev-eu.nynja.net" + } + + var queryItems: HTTPQueryItems? { + return nil + } + + var path: String { + switch self { + case .mint: + return "/nynja/token/api.v.1.0/fund-address" + } + } + + var method: HTTPMethod { + switch self { + case .mint: + return .get + } + } + + var params: HTTPParameters? { + switch self { + case .mint: + return nil + } + } + + var headers: HTTPHeaders { + return [:] + } +} diff --git a/Nynja/WalletFundingNetworkService/WalletFundingNetworkService.swift b/Nynja/WalletFundingNetworkService/WalletFundingNetworkService.swift new file mode 100644 index 000000000..0aa089696 --- /dev/null +++ b/Nynja/WalletFundingNetworkService/WalletFundingNetworkService.swift @@ -0,0 +1,33 @@ +// +// WalletFundingNetworkService.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 8/31/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// +protocol WalletFundingNetworkServiceProtocol { + + func fund(address: String, completion: ((HTTPResponseResult) -> Void)?) +} + +final class WalletFundingNetworkService: WalletFundingNetworkServiceProtocol, InitializeInjectable { + + //MARK: - InitializeInjectable + struct Dependencies { + let client: URLSessionNetworkClient + } + + required init(dependencies: Dependencies) { + client = dependencies.client + } + + private let client: URLSessionNetworkClient + + func fund(address: String, completion: ((HTTPResponseResult) -> Void)?) { + let queryItems: HTTPQueryItems = ["address": address] + let target = WalletFundingNetworkRouter.mint(queryItems: queryItems) + client.request(to: target) { (result: HTTPResponseResult) in + completion?(result) + } + } +} diff --git a/Nynja/WalletMQTTModel.swift b/Nynja/WalletMQTTModel.swift index de4757d7b..c6e03d22d 100644 --- a/Nynja/WalletMQTTModel.swift +++ b/Nynja/WalletMQTTModel.swift @@ -22,6 +22,7 @@ final class WalletMQTTModel: BaseMQTTModel { struct RequestInput { let phoneId: String + let password: String let action: Action let walletAddress: String let walletBalance: NYNMoney @@ -70,7 +71,7 @@ final class WalletMQTTModel: BaseMQTTModel { let service = BertAtom(fromString: Keys.service) let wallet = BertAtom(fromString: Keys.wallet) let walletAddress = Bert.getBin(requestInput.walletAddress) - let phoneId = Bert.getBin(requestInput.phoneId) + let password = Bert.getBin(requestInput.password) let add = BertAtom(fromString: Keys.add) let tuple = BertTuple(fromElements: [service, @@ -78,7 +79,7 @@ final class WalletMQTTModel: BaseMQTTModel { wallet, BertNil(), walletAddress, - phoneId, + password, BertNil(), add]) diff --git a/Podfile b/Podfile index 4504c8bf8..0a9ccddff 100644 --- a/Podfile +++ b/Podfile @@ -39,7 +39,7 @@ def commonPodsForNynja pod 'MaterialComponents/FlexibleHeader', '= 55.3.0' pod 'JTAppleCalendar', '= 7.1.5' - pod 'NynjaSDK', '= 1.5.6' + pod 'NynjaSDK-Wallet', '0.7.0' pod 'CryptoSwift', '= 0.10.0' diff --git a/Podfile.lock b/Podfile.lock index fb4bb8775..6130da5b7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -57,7 +57,7 @@ PODS: - MaterialComponents/private/Application - MDFTextAccessibility (1.2.0) - MulticastDelegateSwift (2.1.1) - - NynjaSDK (1.5.6) + - NynjaSDK-Wallet (0.7.0) - QRCode (2.0) - SDWebImage (4.4.2): - SDWebImage/Core (= 4.4.2) @@ -89,7 +89,7 @@ DEPENDENCIES: - libPhoneNumber-iOS (= 0.9.13) - MaterialComponents/FlexibleHeader (= 55.3.0) - MulticastDelegateSwift (= 2.1.1) - - NynjaSDK (= 1.5.6) + - NynjaSDK-Wallet (= 0.7.0) - QRCode (= 2.0) - SDWebImage (= 4.4.2) - SnapKit (= 4.0.0) @@ -97,7 +97,7 @@ DEPENDENCIES: - TestFairy (= 1.13.4) SPEC REPOS: - https://github.com/cocoapods/specs.git: + https://github.com/CocoaPods/Specs.git: - AutoScrollLabel - AWSCore - AWSS3 @@ -128,7 +128,7 @@ SPEC REPOS: - SwiftyTimer - TestFairy https://nynjagroup.jfrog.io/nynjagroup/api/pods/cocoapods-local: - - NynjaSDK + - NynjaSDK-Wallet EXTERNAL SOURCES: CocoaLumberjack: @@ -164,7 +164,7 @@ SPEC CHECKSUMS: MaterialComponents: 915f4e844400a35db3ea4c710a9af40aa8bcb093 MDFTextAccessibility: 94098925e0853551c5a311ce7c1ecefbe297cdb6 MulticastDelegateSwift: 93eb077c24f50574b3f8a3f23bf71be6de6e3b41 - NynjaSDK: 83e97b19149b19ffd5219bca3c5ef4a9906b1b0f + NynjaSDK-Wallet: 2adec1b57b9196c8886144c7612ccc03f7263e98 QRCode: f98a1886c8f37523704a7512a4c0cd45b34c18a4 SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681 SnapKit: a42d492c16e80209130a3379f73596c3454b7694 @@ -173,6 +173,6 @@ SPEC CHECKSUMS: SwiftyTimer: 2efd74b060d69ad4f1496baf5bbedbe132125fcf TestFairy: 842f8ddc45477b208eb85326b0418047b40f7137 -PODFILE CHECKSUM: 3b17e606e9151963b9ee603546dc1a061f6ce1ec +PODFILE CHECKSUM: 1955caeb78e6c96930b6aa61dda6f62141b5b90d -COCOAPODS: 1.5.3 +COCOAPODS: 1.5.0 -- GitLab From db2d0d0fb239fae8750402c83b021202e5bf85e4 Mon Sep 17 00:00:00 2001 From: Aleksandr Pavliuk Date: Fri, 5 Oct 2018 10:22:58 +0300 Subject: [PATCH 2/3] Changes according to code review --- Nynja.xcodeproj/project.pbxproj | 34 +++++++------- .../Models/Feature/FeatureExtension.swift | 2 +- .../Extensions/Models/ProfileExtension.swift | 11 +++-- .../Models/ServiceWalletExtension.swift | 25 ++++++++++ .../Message/Presenter/MessagePresenter.swift | 4 +- .../View/CreateWalletViewController.swift | 8 ++-- .../Interactor/PaymentInteractor.swift | 24 ++++------ .../Payment/Presenter/PaymentPresenter.swift | 5 +- .../Payment/WireFrame/PaymentWireFrame.swift | 6 +-- .../SeedVerificationWalletInteractor.swift | 2 +- .../Interactor/WalletBalancesInteractor.swift | 41 +++++----------- .../WireFrame/WalletBalancesWireFrame.swift | 1 - .../Interactor/WalletDetailsInteractor.swift | 3 -- .../WireFrame/WalletDetailsWireFrame.swift | 1 - .../ModelAdapters/ContactServices.swift | 28 ----------- .../ModelAdapters/ProfileServices.swift | 46 ------------------ .../Entities/TransferParams.swift | 16 +++++++ .../WalletService/Entities/WalletParams.swift | 15 ++++++ .../WalletService/WalletService.swift | 47 +++++++------------ .../Models/Contact/ContactExtension.swift | 14 +++++- 20 files changed, 142 insertions(+), 191 deletions(-) create mode 100644 Nynja/Extensions/Models/ServiceWalletExtension.swift delete mode 100644 Nynja/ServerModel/ModelAdapters/ContactServices.swift delete mode 100644 Nynja/ServerModel/ModelAdapters/ProfileServices.swift create mode 100644 Nynja/Services/WalletService/Entities/TransferParams.swift create mode 100644 Nynja/Services/WalletService/Entities/WalletParams.swift diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index c9991dcc5..bc7ba77f9 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -2142,8 +2142,6 @@ FBCE83D320E52352003B7558 /* PaymentInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83CD20E52351003B7558 /* PaymentInteractor.swift */; }; FBCE83D520E52397003B7558 /* MQTTServiceWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */; }; FBCE83D720E523A8003B7558 /* WalletMQTTModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83D620E523A8003B7558 /* WalletMQTTModel.swift */; }; - FBCE83E020E52496003B7558 /* ContactServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83DE20E52496003B7558 /* ContactServices.swift */; }; - FBCE83E120E52496003B7558 /* ProfileServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83DF20E52496003B7558 /* ProfileServices.swift */; }; FBCE840D20E525A6003B7558 /* HTTPResponseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83FA20E525A5003B7558 /* HTTPResponseResult.swift */; }; FBCE840E20E525A6003B7558 /* HTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83FC20E525A5003B7558 /* HTTPMethod.swift */; }; FBCE840F20E525A6003B7558 /* HTTPParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBCE83FD20E525A5003B7558 /* HTTPParameters.swift */; }; @@ -2175,6 +2173,12 @@ FE58F9B3208F0583004AFDD3 /* DBMessageEditAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE58F9B2208F0583004AFDD3 /* DBMessageEditAction.swift */; }; FE9E70D021175DDC0034067A /* ChatScreenAlertFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9E70CF21175DDC0034067A /* ChatScreenAlertFactory.swift */; }; FEA59F90B93C7B49BAF99F9C /* SelectCountryProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F7C039B61A0663D43BE5AE5 /* SelectCountryProtocols.swift */; }; + FECFE90A21673CCD000922DE /* ServiceWalletExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECFE90921673CCD000922DE /* ServiceWalletExtension.swift */; }; + FECFE90B21673CCD000922DE /* ServiceWalletExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECFE90921673CCD000922DE /* ServiceWalletExtension.swift */; }; + FECFE91021673E26000922DE /* WalletParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECFE90F21673E26000922DE /* WalletParams.swift */; }; + FECFE91121673E26000922DE /* WalletParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECFE90F21673E26000922DE /* WalletParams.swift */; }; + FECFE91321673E5E000922DE /* TransferParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECFE91221673E5E000922DE /* TransferParams.swift */; }; + FECFE91421673E5E000922DE /* TransferParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECFE91221673E5E000922DE /* TransferParams.swift */; }; FFA42A7A0068D38108EAD34E /* TopUpAccountProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3D79F4278D6CC5428C4F6B /* TopUpAccountProtocols.swift */; }; /* End PBXBuildFile section */ @@ -4102,8 +4106,6 @@ FBCE83CD20E52351003B7558 /* PaymentInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentInteractor.swift; sourceTree = ""; }; FBCE83D420E52396003B7558 /* MQTTServiceWallet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTServiceWallet.swift; sourceTree = ""; }; FBCE83D620E523A8003B7558 /* WalletMQTTModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletMQTTModel.swift; sourceTree = ""; }; - FBCE83DE20E52496003B7558 /* ContactServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactServices.swift; sourceTree = ""; }; - FBCE83DF20E52496003B7558 /* ProfileServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileServices.swift; sourceTree = ""; }; FBCE83FA20E525A5003B7558 /* HTTPResponseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPResponseResult.swift; sourceTree = ""; }; FBCE83FC20E525A5003B7558 /* HTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPMethod.swift; sourceTree = ""; }; FBCE83FD20E525A5003B7558 /* HTTPParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPParameters.swift; sourceTree = ""; }; @@ -4135,6 +4137,9 @@ FE58F9B0208F00FE004AFDD3 /* MessageEditActionTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEditActionTable.swift; sourceTree = ""; }; FE58F9B2208F0583004AFDD3 /* DBMessageEditAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMessageEditAction.swift; sourceTree = ""; }; FE9E70CF21175DDC0034067A /* ChatScreenAlertFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatScreenAlertFactory.swift; sourceTree = ""; }; + FECFE90921673CCD000922DE /* ServiceWalletExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWalletExtension.swift; sourceTree = ""; }; + FECFE90F21673E26000922DE /* WalletParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletParams.swift; sourceTree = ""; }; + FECFE91221673E5E000922DE /* TransferParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferParams.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -9650,7 +9655,6 @@ A44A2C962057E3E8009863B6 /* ServerModel */ = { isa = PBXGroup; children = ( - FBCE83DD20E52496003B7558 /* ModelAdapters */, A42CE4D220692EDA000889CC /* Model */, A42CE50720692EDA000889CC /* Source */, A42CE50C20692EDA000889CC /* Spec */, @@ -11801,6 +11805,7 @@ 263529142075729400DC6FBD /* Job+DB.swift */, 00E9825D205FDB1A008BF03D /* AuthExtension.swift */, A438DB9120763AFB00AA86A2 /* Contact+Desc.swift */, + FECFE90921673CCD000922DE /* ServiceWalletExtension.swift */, ); path = Models; sourceTree = ""; @@ -13010,6 +13015,8 @@ children = ( FB60B47E215E618200B59279 /* WalletServiceResult.swift */, FB60B47F215E618200B59279 /* WalletServiceWallet.swift */, + FECFE90F21673E26000922DE /* WalletParams.swift */, + FECFE91221673E5E000922DE /* TransferParams.swift */, ); path = Entities; sourceTree = ""; @@ -13257,15 +13264,6 @@ path = Interactor; sourceTree = ""; }; - FBCE83DD20E52496003B7558 /* ModelAdapters */ = { - isa = PBXGroup; - children = ( - FBCE83DE20E52496003B7558 /* ContactServices.swift */, - FBCE83DF20E52496003B7558 /* ProfileServices.swift */, - ); - path = ModelAdapters; - sourceTree = ""; - }; FBCE83F720E525A5003B7558 /* REST */ = { isa = PBXGroup; children = ( @@ -14159,6 +14157,7 @@ 263529182075730500DC6FBD /* actExtension+BERT.swift in Sources */, 85A3CA03214133FD00E0EDD5 /* KeyboardInteractive.swift in Sources */, A4679B9020B2DA640021FE9C /* SelectorAvatarCell.swift in Sources */, + FECFE91421673E5E000922DE /* TransferParams.swift in Sources */, 8509FC7E2158DD9A00734D93 /* Feature+Construct.swift in Sources */, 26C0C1EB2073DC1400C530DA /* ForwardSelectorDataSource.swift in Sources */, 265F5D26209B6AF7008ACCC8 /* Place.swift in Sources */, @@ -14171,6 +14170,7 @@ A42CE56820692EDB000889CC /* Profile.swift in Sources */, A42CE55820692EDB000889CC /* Index.swift in Sources */, 2691B76D2075504A00FB207C /* MQTTServiceSchedule.swift in Sources */, + FECFE90B21673CCD000922DE /* ServiceWalletExtension.swift in Sources */, 267D465620AB45D200D42242 /* LanguageSettingProtocol.swift in Sources */, F11786E820AC3562007A9A1B /* Bundle+Keys.swift in Sources */, 269848CB200EA0B400590D6F /* StarExtension+BERT.swift in Sources */, @@ -14193,6 +14193,7 @@ A42CE56620692EDB000889CC /* receiveTask.swift in Sources */, A42CE5E220692EDB000889CC /* ExtendedStar_Spec.swift in Sources */, A42CE61220692EDB000889CC /* Typing_Spec.swift in Sources */, + FECFE91121673E26000922DE /* WalletParams.swift in Sources */, A42CE5DC20692EDB000889CC /* iterator_Spec.swift in Sources */, FB61D663214FF96200CB2A1F /* ColorsConstants.swift in Sources */, A42CE5CA20692EDB000889CC /* Profile_Spec.swift in Sources */, @@ -14360,7 +14361,6 @@ 85CE26D820C5593600553FE7 /* HapticSelectionFeedbackGenerator.swift in Sources */, A49381AA21355EE1006D28DD /* MessageInteractor+Forward.swift in Sources */, 26FA4210201821B400E6F6EC /* StarHandler.swift in Sources */, - FBCE83E120E52496003B7558 /* ProfileServices.swift in Sources */, 26C0C1E42073DA3A00C530DA /* Muc+DB.swift in Sources */, 4BE2C5DA2142EAC500A73DD9 /* AudioSessionManager.swift in Sources */, 00E98252205C26F7008BF03D /* SessionLineView.swift in Sources */, @@ -14944,6 +14944,7 @@ 26541F722007B93400AAEACF /* DBMessageAction.swift in Sources */, 264638251FFFE78E002590E6 /* RepliesWireFrame.swift in Sources */, 005886CB2030F3F900FE2E89 /* NynjaTimeMinsDelegate.swift in Sources */, + FECFE91021673E26000922DE /* WalletParams.swift in Sources */, 0F409A888929B0CC8EDF6656 /* QRCodeReaderPresenter.swift in Sources */, 2646381E1FFFC5CB002590E6 /* RepliesProtocols.swift in Sources */, 38182BD2C2E0C783796C8AA1 /* QRCodeReaderInteractor.swift in Sources */, @@ -15023,6 +15024,7 @@ A43B25AB20AB1DFA00FF8107 /* ALTextView.swift in Sources */, FB60B44D215E611A00B59279 /* WalletDetailsInteractor.swift in Sources */, 4BDC7E61203492CA00BCD381 /* TopSwipable.swift in Sources */, + FECFE91321673E5E000922DE /* TransferParams.swift in Sources */, A4626EB320D96FAE000F37EE /* TopLevelInfo.swift in Sources */, FB60B490215E671400B59279 /* WalletBalancesViewModel.swift in Sources */, 267BE90C2069405200153FB8 /* StarMessageDAOProtocol.swift in Sources */, @@ -15428,7 +15430,6 @@ F119E67920D27EA50043A532 /* VideoPreviewCVCell.swift in Sources */, 8502DB542061030100613C8C /* WheelPositionPickerViewController.swift in Sources */, A45F116120B422AF00F45004 /* Message+System.swift in Sources */, - FBCE83E020E52496003B7558 /* ContactServices.swift in Sources */, 4B06D30C2028A25D003B275B /* HomeItemsFactory.swift in Sources */, 266AE8C3203496B60096A12C /* AsyncOperation.swift in Sources */, F119E67220D24BE40043A532 /* MultiplePreviewInteractor.swift in Sources */, @@ -15771,6 +15772,7 @@ 990A25B2C84CE09B4CE64533 /* MyGroupAliasPresenter.swift in Sources */, 4BE2C5E82142EB5A00A73DD9 /* NynjaRingingService.swift in Sources */, 4B06D3202028A9B1003B275B /* P2pChatItemsFactory.swift in Sources */, + FECFE90A21673CCD000922DE /* ServiceWalletExtension.swift in Sources */, A432CF1820B4347D00993AFB /* MaterialTextField.swift in Sources */, FB60B44F215E611A00B59279 /* WalletDetailsViewInput.swift in Sources */, 8514F17420EA219E00883513 /* ContextMenuNextCell.swift in Sources */, diff --git a/Nynja/Extensions/Models/Feature/FeatureExtension.swift b/Nynja/Extensions/Models/Feature/FeatureExtension.swift index 08082a795..11bb86946 100644 --- a/Nynja/Extensions/Models/Feature/FeatureExtension.swift +++ b/Nynja/Extensions/Models/Feature/FeatureExtension.swift @@ -81,13 +81,13 @@ enum FeatureKeys { } enum FeatureGroup: String { + case none = "" case authData = "AUTH_DATA" case jobTimezone = "JOB_TIMEZONE" case channelData = "CHANNEL_DATA" case fileData = "FILE_DATA" case callData = "CALL_DATA" case pushSettings = "PUSH_SETTINGS" - case none = "" case payment = "TRANSACTION_DATA" } diff --git a/Nynja/Extensions/Models/ProfileExtension.swift b/Nynja/Extensions/Models/ProfileExtension.swift index 24e01e36a..5fd0ebc3f 100644 --- a/Nynja/Extensions/Models/ProfileExtension.swift +++ b/Nynja/Extensions/Models/ProfileExtension.swift @@ -40,9 +40,12 @@ extension Profile: DBModelConvertible { } extension Profile { - - var nynBalance: NYNMoney { - let amount = balance.map { Decimal($0) } ?? 0 - return NYNMoney(amount) + var wallet: Service? { + guard let services = services else { + return nil + } + return services.first(where: { (service) -> Bool in + return true + }) } } diff --git a/Nynja/Extensions/Models/ServiceWalletExtension.swift b/Nynja/Extensions/Models/ServiceWalletExtension.swift new file mode 100644 index 000000000..b0c62e2f8 --- /dev/null +++ b/Nynja/Extensions/Models/ServiceWalletExtension.swift @@ -0,0 +1,25 @@ +// +// ServiceWalletExtension.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 10/5/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +extension Service { + + func isWalletType() -> Bool { + return isService(hasType: "wallet") + } + + private func stringAtom(_ object: AnyObject) -> StringAtom? { + return object as? StringAtom + } + + private func isService(hasType type: String) -> Bool { + guard let typeObject = self.type, let typeAtom = stringAtom(typeObject) else { + return false + } + return typeAtom.string == type + } +} diff --git a/Nynja/Modules/Message/Presenter/MessagePresenter.swift b/Nynja/Modules/Message/Presenter/MessagePresenter.swift index 771542b31..50d19a0f3 100644 --- a/Nynja/Modules/Message/Presenter/MessagePresenter.swift +++ b/Nynja/Modules/Message/Presenter/MessagePresenter.swift @@ -64,8 +64,8 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract assertionFailure("user profile should exist") return false } - let opponentWallet = ContactServices(contact: contact).wallet != nil - let myWallet = ProfileServices(profile: profile).wallet != nil + let opponentWallet = contact.wallet != nil + let myWallet = profile.wallet != nil return opponentWallet && myWallet } diff --git a/Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift b/Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift index a8604ad8d..d6dac23e4 100644 --- a/Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift +++ b/Nynja/Modules/Wallet Flows/CreateWallet/View/CreateWalletViewController.swift @@ -195,10 +195,10 @@ private extension CreateWalletViewController { //MARK: CreateWalletViewProtocol extension CreateWalletViewController { func setup(with validationResult: CreateWalletValidationViewModel) { - self.walletNameTextField.info = validationResult.name - self.pinTextField.info = validationResult.passcode - self.pinConfirmTextField.info = validationResult.passcodeConfirmation - self.seedButton.isEnabled = validationResult.valid + walletNameTextField.info = validationResult.name + pinTextField.info = validationResult.passcode + pinConfirmTextField.info = validationResult.passcodeConfirmation + seedButton.isEnabled = validationResult.valid } } diff --git a/Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift b/Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift index db481843c..8183f7ad9 100644 --- a/Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift +++ b/Nynja/Modules/Wallet Flows/Payment/Interactor/PaymentInteractor.swift @@ -12,12 +12,10 @@ final class PaymentInteractor: PaymentInteractorInputProtocol, SetInjectable { private weak var presenter: PaymentInteractorOutputProtocol! private var recipientContact: Contact! - private var contactServices: ContactServices! private var walletService: WalletServiceProtocol! private var messageSendingService: MessageSendingServiceProtocol! private var messageFactory: MessageFactoryProtocol! private var profile: Profile! - private var profileServices: ProfileServices! private var inputValidationService: WalletOpeningTextInputValidationServiceProtocol! private var amount: NYNMoney? = nil @@ -56,7 +54,7 @@ final class PaymentInteractor: PaymentInteractorInputProtocol, SetInjectable { func getWalletName() -> String? { // Wallet inplementation should be changed on server side, until this wallet name will be stored in password field - return profileServices.wallet?.password + return profile.wallet?.password } func validate(passcode: String) -> Bool { @@ -102,9 +100,7 @@ extension PaymentInteractor { let messageSendingService: MessageSendingServiceProtocol let messageFactory: MessageFactoryProtocol let profile: Profile - let profileServices: ProfileServices let inputValidationService: WalletOpeningTextInputValidationServiceProtocol - let contactServices: ContactServices } func inject(dependencies: PaymentInteractor.Dependencies) { @@ -114,9 +110,7 @@ extension PaymentInteractor { messageSendingService = dependencies.messageSendingService messageFactory = dependencies.messageFactory profile = dependencies.profile - profileServices = dependencies.profileServices inputValidationService = dependencies.inputValidationService - contactServices = dependencies.contactServices } } @@ -159,20 +153,20 @@ private extension PaymentInteractor { } func makeTransfer(passcode: String, completion: @escaping (Error?) -> ()) { - guard let receipientAddress = contactServices.wallet?.login, let amountString = amount?.description else { + guard let receipientAddress = recipientContact.wallet?.login, let amountString = amount?.description else { completion(PaymentInteractorError.cantSendCoins) return } let name = getWalletName() - walletService.makeTransfer( - name: name, - passcode: passcode, - receipientAddress: receipientAddress, - tockenType: amountString, - amount: amountString) { (result) in - completion(result.error) + walletService.sendCoins( + params: TransferParams( + name: name, + passcode: passcode, + receipientAddress: receipientAddress, + amount: amountString)) { (result) in + completion(result.error) } } } diff --git a/Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift b/Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift index f9c40bbee..0a45ce2fb 100644 --- a/Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift +++ b/Nynja/Modules/Wallet Flows/Payment/Presenter/PaymentPresenter.swift @@ -125,11 +125,8 @@ final class PaymentPresenter: BasePresenter, SetInjectable, PaymentPresenterProt } let name = interactor.getWalletName() interactor.setupWallet(with: name, passcode: passcode) { [weak self] (error) in - guard let `self` = self else { - return - } dispatchAsyncMain { - self.view.hideActivityIndicator() + self?.view.hideActivityIndicator() } } } diff --git a/Nynja/Modules/Wallet Flows/Payment/WireFrame/PaymentWireFrame.swift b/Nynja/Modules/Wallet Flows/Payment/WireFrame/PaymentWireFrame.swift index ce0a5dba9..9a7dd0498 100644 --- a/Nynja/Modules/Wallet Flows/Payment/WireFrame/PaymentWireFrame.swift +++ b/Nynja/Modules/Wallet Flows/Payment/WireFrame/PaymentWireFrame.swift @@ -33,8 +33,6 @@ final class PaymentWireFrame: PaymentWireFrameProtocol { let messageSendingService = serviceFactory.makeMessageSendingService() let walletService = serviceFactory.makeWalletService() let messageFactory = serviceFactory.makeMessageFactory() - let profileService = ProfileServices(profile: profile) - let contactServices = ContactServices(contact: contact) let interactorDependencies = PaymentInteractor.Dependencies( presenter: presenter, @@ -43,9 +41,7 @@ final class PaymentWireFrame: PaymentWireFrameProtocol { messageSendingService: messageSendingService, messageFactory: messageFactory, profile: profile, - profileServices: profileService, - inputValidationService: inputValidationService, - contactServices: contactServices) + inputValidationService: inputValidationService) interactor.inject(dependencies: interactorDependencies) // Presenting diff --git a/Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift b/Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift index b22d6216e..1a2c5b0c6 100644 --- a/Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift +++ b/Nynja/Modules/Wallet Flows/SeedVerification/Interactor/SeedVerificationWalletInteractor.swift @@ -31,7 +31,7 @@ extension SeedVerificationWalletInteractor { } func createWallet(completion: @escaping (Error?) -> ()) { - walletService.makeNynjaWallet(seeds: seeds, name: name, passcode: passcode) { (result) in + walletService.makeNynjaWallet(params: WalletParams(seeds: seeds, name: name, passcode: passcode)) { (result) in completion(result.error) } } diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift b/Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift index eac31c52f..6a7b764ea 100644 --- a/Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift +++ b/Nynja/Modules/Wallet Flows/WalletBalances/Interactor/WalletBalancesInteractor.swift @@ -12,18 +12,24 @@ final class WalletBalancesInteractor: WalletBalancesInteractorInputProtocol, Set private weak var presenter: WalletBalancesInteractorOutputProtocol! private var profile: Profile! private var walletService: WalletServiceProtocol! - private var profileServices: ProfileServicesProtocol! private var inputValidationService: WalletOpeningTextInputValidationServiceProtocol! private var wallet: WalletBalancesWallet? //MARK: WalletBalancesInteractorInputProtocol func getWallet(name: String?, passcode: String, completion: @escaping (WalletBalancesWallet?, Error?) -> ()) { - fetchWallet(name: name, passcode: passcode) { [weak self] (walletResult, error) in - if walletResult != nil { - self?.wallet = walletResult + walletService.getWallet(name: name, passcode: passcode) { (result) in + switch result { + case .success(let walletFromSDK): + let amount = Decimal(string: walletFromSDK.account.balance)! + let wallet = WalletBalancesWallet( + nynjaCoin: NYNMoney(amount), + nynjaWalletAddress: walletFromSDK.account.address, + ethereum: nil, + name: walletFromSDK.name) + completion(wallet, nil) + case .failure(let error): + completion(nil, error) } - - completion(walletResult, error) } } @@ -37,7 +43,7 @@ final class WalletBalancesInteractor: WalletBalancesInteractorInputProtocol, Set func getWalletName() -> String? { // Wallet inplementation should be changed on server side, until this wallet name will be stored in password field - return profileServices.wallet?.password + return profile.wallet?.password } func validate(passcode: String) -> Bool { @@ -51,31 +57,11 @@ final class WalletBalancesInteractor: WalletBalancesInteractorInputProtocol, Set } } -private extension WalletBalancesInteractor { - func fetchWallet(name: String?, passcode: String, completion: @escaping (WalletBalancesWallet?, Error?) -> ()) { - walletService.getWallet(name: name, passcode: passcode) { (result) in - switch result { - case .success(let walletFromSDK): - let amount = Decimal(string: walletFromSDK.account.balance)! - let wallet = WalletBalancesWallet( - nynjaCoin: NYNMoney(amount), - nynjaWalletAddress: walletFromSDK.account.address, - ethereum: nil, - name: walletFromSDK.name) - completion(wallet, nil) - case .failure(let error): - completion(nil, error) - } - } - } -} - extension WalletBalancesInteractor { struct Dependencies { let presenter: WalletBalancesInteractorOutputProtocol let profile: Profile let walletService: WalletServiceProtocol - let profileServices: ProfileServicesProtocol let inputValidationService: WalletOpeningTextInputValidationServiceProtocol } @@ -83,7 +69,6 @@ extension WalletBalancesInteractor { presenter = dependencies.presenter profile = dependencies.profile walletService = dependencies.walletService - profileServices = dependencies.profileServices inputValidationService = dependencies.inputValidationService } } diff --git a/Nynja/Modules/Wallet Flows/WalletBalances/WireFrame/WalletBalancesWireFrame.swift b/Nynja/Modules/Wallet Flows/WalletBalances/WireFrame/WalletBalancesWireFrame.swift index 0b5913d1b..8edae16b3 100644 --- a/Nynja/Modules/Wallet Flows/WalletBalances/WireFrame/WalletBalancesWireFrame.swift +++ b/Nynja/Modules/Wallet Flows/WalletBalances/WireFrame/WalletBalancesWireFrame.swift @@ -36,7 +36,6 @@ final class WalletBalancesWireFrame: WalletBalancesWireFrameProtocol { presenter: presenter, profile: profile, walletService: walletService, - profileServices: ProfileServices(profile: profile), inputValidationService: inputValidationService) interactor.inject(dependencies: interactorDependencies) diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift b/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift index 0789143a6..6076b21c9 100644 --- a/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift +++ b/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift @@ -12,7 +12,6 @@ final class WalletDetailsInteractor: WalletDetailsInteractorInputProtocol { private weak var presenter: WalletDetailsInteractorOutputProtocol! private var profile: Profile! - private var profileServices: ProfileServices! private var input: WalletDetailsViewInput! //MARK: WalletDetailsInteractorInputProtocol @@ -42,14 +41,12 @@ extension WalletDetailsInteractor: SetInjectable { struct Dependencies { let presenter: WalletDetailsInteractorOutputProtocol let profile: Profile - let profileServices: ProfileServices let input: WalletDetailsViewInput } func inject(dependencies: WalletDetailsInteractor.Dependencies) { presenter = dependencies.presenter profile = dependencies.profile - profileServices = dependencies.profileServices input = dependencies.input } } diff --git a/Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift b/Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift index 26795194e..3a1a41c4c 100644 --- a/Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift +++ b/Nynja/Modules/Wallet Flows/WalletDetails/WireFrame/WalletDetailsWireFrame.swift @@ -32,7 +32,6 @@ final class WalletDetailsWireFrame: WalletDetailsWireFrameProtocol { let interactorDependencies = WalletDetailsInteractor.Dependencies( presenter: presenter, profile: profile, - profileServices: ProfileServices(profile: profile), input: input) interactor.inject(dependencies: interactorDependencies) diff --git a/Nynja/ServerModel/ModelAdapters/ContactServices.swift b/Nynja/ServerModel/ModelAdapters/ContactServices.swift deleted file mode 100644 index 809b4f18a..000000000 --- a/Nynja/ServerModel/ModelAdapters/ContactServices.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// ContactServices.swift -// Nynja -// -// Created by Aleksander Pavliuk on 6/12/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -protocol ContactServicesProtocol { - var wallet: Service? { get } -} - -class ContactServices: ServicesDecorator, ContactServicesProtocol { - - let contact: Contact - - init(contact: Contact) { - self.contact = contact - } - - var wallet: Service? { - guard let services = contact.services else { - return nil - } - return services.first(where: isWalletType) - - } -} diff --git a/Nynja/ServerModel/ModelAdapters/ProfileServices.swift b/Nynja/ServerModel/ModelAdapters/ProfileServices.swift deleted file mode 100644 index 57cb974e7..000000000 --- a/Nynja/ServerModel/ModelAdapters/ProfileServices.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// ProfileServices.swift -// Nynja -// -// Created by Aleksander Pavliuk on 6/12/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -protocol ProfileServicesProtocol { - var wallet: Service? { get } -} - -class ProfileServices: ServicesDecorator, ProfileServicesProtocol { - - let profile: Profile - - init(profile: Profile) { - self.profile = profile - } - - var wallet: Service? { - guard let services = profile.services else { - return nil - } - return services.first(where: isWalletType) - } -} - -class ServicesDecorator { - - func isWalletType(service: Service) -> Bool { - return isService(service, hasType: "wallet") - } - - private func stringAtom(_ object: AnyObject) -> StringAtom? { - return object as? StringAtom - } - - private func isService(_ service: Service, hasType type: String) -> Bool { - guard let typeObject = service.type, let typeAtom = stringAtom(typeObject) else { - return false - } - return typeAtom.string == type - } -} - diff --git a/Nynja/Services/WalletService/Entities/TransferParams.swift b/Nynja/Services/WalletService/Entities/TransferParams.swift new file mode 100644 index 000000000..2623a8638 --- /dev/null +++ b/Nynja/Services/WalletService/Entities/TransferParams.swift @@ -0,0 +1,16 @@ +// +// TransferParams.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 10/5/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +struct TransferParams { + let name: String? + let passcode: String + let receipientAddress: String + let amount: String +} diff --git a/Nynja/Services/WalletService/Entities/WalletParams.swift b/Nynja/Services/WalletService/Entities/WalletParams.swift new file mode 100644 index 000000000..734930922 --- /dev/null +++ b/Nynja/Services/WalletService/Entities/WalletParams.swift @@ -0,0 +1,15 @@ +// +// WalletParams.swift +// Nynja +// +// Created by Aleksandr Pavliuk on 10/5/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +struct WalletParams { + let seeds: [String] + let name: String + let passcode: String +} diff --git a/Nynja/Services/WalletService/WalletService.swift b/Nynja/Services/WalletService/WalletService.swift index 4dcc642cb..c7db42e15 100644 --- a/Nynja/Services/WalletService/WalletService.swift +++ b/Nynja/Services/WalletService/WalletService.swift @@ -9,20 +9,12 @@ import NynjaSDK.NynjaWallet protocol WalletServiceProtocol { - func makeNynjaWallet(seeds: [String], - name: String, - passcode: String, - completion: (WalletServiceResult) -> ()) + func makeNynjaWallet(params: WalletParams, completion: (WalletServiceResult) -> ()) func makeMnemonics(completion: (WalletServiceResult<[String]>) -> ()) func getWallet(name: String?, passcode: String, completion: @escaping (WalletServiceResult) -> ()) - func makeTransfer(name: String?, - passcode: String, - receipientAddress: String, - tockenType: String?, - amount: String, - completion: @escaping (WalletServiceResult) -> ()) + func sendCoins(params: TransferParams, completion: @escaping (WalletServiceResult) -> ()) } final class WalletService: InitializeInjectable, WalletServiceProtocol { @@ -50,18 +42,16 @@ final class WalletService: InitializeInjectable, WalletServiceProtocol { //MARK: WalletServiceProtocol extension WalletService { - func makeNynjaWallet(seeds: [String], - name: String, - passcode: String, - completion: (WalletServiceResult) -> ()) { + func makeNynjaWallet(params: WalletParams, completion: (WalletServiceResult) -> ()) { do { - let account = try makeWalletWithSingleAccount(seeds: seeds, name: name, passcode: passcode) + let account = try makeWalletWithSingleAccount( + seeds: params.seeds, + name: params.name, + passcode: params.passcode) let address = try account.getAddress() walletFundingNetworkService.fund(address: address, completion: nil) - - mqttService.addWalletToProfile(walletAddress: address, balance: NYNMoney(0), password: name) - - completion(.success(name)) + mqttService.addWalletToProfile(walletAddress: address, balance: NYNMoney(0), password: params.name) + completion(.success(params.name)) } catch { completion(.failure(error)) } @@ -95,20 +85,15 @@ extension WalletService { } } - func makeTransfer(name: String?, - passcode: String, - receipientAddress: String, - tockenType: String? = nil, - amount: String, - completion: @escaping (WalletServiceResult) -> ()) { + func sendCoins(params: TransferParams, completion: @escaping (WalletServiceResult) -> ()) { do { - let account = try getAccountOfWallet(with: name) + let account = try getAccountOfWallet(with: params.name) try account.sendCoins( - receipientAddress, - withTokenType: tockenType, - withPassphrase: passcode, - withAmount: amount) { (transactionId, error) in - completion(.success(transactionId)) + params.receipientAddress, + withTokenType: String(NynjaSDK.ETH.rawValue), + withPassphrase: params.passcode, + withAmount: params.amount) { (transactionId, error) in + completion(.success(transactionId)) } } catch { completion(.failure(error)) diff --git a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift index 0cc738fe8..fdbe9f096 100644 --- a/Shared/Library/Extensions/Models/Contact/ContactExtension.swift +++ b/Shared/Library/Extensions/Models/Contact/ContactExtension.swift @@ -235,5 +235,17 @@ extension Contact { // MARK: - CellModel extension Contact: CellModel { - + +} + +extension Contact { + var wallet: Service? { + guard let services = services else { + return nil + } + + return services.first(where: { (service) -> Bool in + return service.isWalletType() + }) + } } -- GitLab From be88983efef4747d81dc7baa1cf15787e8d3727a Mon Sep 17 00:00:00 2001 From: Aleksandr Pavliuk Date: Fri, 5 Oct 2018 12:01:55 +0300 Subject: [PATCH 3/3] Fixed profile patching to make wallet accessable after creation --- .../Services/HandleServices/ProfileHandler.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Nynja/Services/HandleServices/ProfileHandler.swift b/Nynja/Services/HandleServices/ProfileHandler.swift index 9556c75db..bf206bb91 100644 --- a/Nynja/Services/HandleServices/ProfileHandler.swift +++ b/Nynja/Services/HandleServices/ProfileHandler.swift @@ -40,7 +40,7 @@ class ProfileHandler: BaseHandler { } switch status { - case "get", "init", "patch": + case "get", "init": if !storageService.isUserLogined, let phoneId = profile.phoneId { LogService.log(topic: .db) { return "Setup DB: Prifile Handler" } storageService.setupDatabase(with: phoneId, application: UIApplication.shared) @@ -48,6 +48,8 @@ class ProfileHandler: BaseHandler { handleGetInit(profile) messageBackgroundTaskHandler.performTask() + case "patch": + handlePatch(profile) case "remove": handleRemove(profile) default: @@ -59,7 +61,7 @@ class ProfileHandler: BaseHandler { // MARK: - Statuses // MARK: Get & Init - + private static func handleGetInit(_ profile: Profile) { do { guard let roster = (profile.rosters as? [Roster])?.first else { @@ -81,6 +83,16 @@ class ProfileHandler: BaseHandler { } catch { } } + + // MARK: Patch + private static func handlePatch(_ newProfile: Profile) { + guard let profile = ProfileDAO.currentProfile, let newServices = newProfile.services else { + return + } + + profile.services = newServices + try? storageService.perform(action: .save, with: profile) + } private static func prepareForReceived(_ newRoster: Roster) { let currentRoster = RosterDAO.currentRoster -- GitLab