diff --git a/Frameworks/NynjaUIKit/NynjaUIKit/Views/ContextMenu/Model/ContextMenuItem.swift b/Frameworks/NynjaUIKit/NynjaUIKit/Views/ContextMenu/Model/ContextMenuItem.swift index 4f0ade68fb5669d1276c88df4f901519be4f2e15..b475bf0da1da437cd1ff983a1962d04cc2eac1da 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 e95210b3eaa81b8ca8fe3b73f1972e510256b919..c9991dcc56acee841616ca9b310908c6886d8428 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 7d7c725d58ef62814de58c57ec540517e647b534..0fc1158deecfb584f3727c37b820ce9faa9b6601 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 0f7588448a84ff72c185488969a889b417fb2f2c..08082a7952e1664e948945528e3792e4842690bc 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 d0efb3e9196da503b254b256d4137c332c66c161..803ea62b1f71ef9906b90cd7e767723bbaf1fb6f 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 b2a681105a5524d3e7150d4cd33c94e44fc17c84..fd263d053b02346076df39e418fd4c3cdebcb61c 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 50c48a0aee45d3656227e3bc727ccc110d2e738d..2928f1e7b5259ad030402a6969e5bd8f66363a70 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 0326fd7c681d085a9962105a0a3b2d1f2c7ff8cf..6ca6b294d5ce2a8d23b024b47067bef0c849e5ba 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 5fde105e4cbc16d472d53835e7e7db9a9f0a5c41..3e19e0772ee4d4213ebe0581ec0244f0100ab4ed 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 f96280473d34153cf7ba6b9e7450f12728300969..3c960b9aa9aeff41143aa699133af7ada824b9d9 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 c1957a6d0a4fce5bfa5482e2777907b3ab9eef04..ee4173bed9f7604edcdc32d57f6dc1e7495c1044 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.. Void typealias MTIShouldChangeTextHandler = (MaterialTextInput, NSRange, String) -> Bool +typealias MTIShouldBeginEditing = (MaterialTextInput) -> Bool protocol MaterialTextInput: FloatingPlaceholderProvider, InputInfoProvider { diff --git a/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift b/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift index f4e99e0954b8522c08f98a6e89cd6d760b08f440..ff8a5dcdab510b259b4d27478d2932fed811b8ba 100644 --- a/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift +++ b/Nynja/Library/UI/TextInput/Material/Config/NynjaMTIConfig.swift @@ -17,6 +17,7 @@ struct NynjaMTIConfig: MTIConfigProtocol { let cursorColor: UIColor = defaultColor let keyboardType: UIKeyboardType = .default + let isSecureTextEntry = false let placeholderFont: UIFont = defaultFont let placeholderCollapsedFontSize: CGFloat = UIFont.makeFont(with: FontFamily.NotoSans.regular.name, diff --git a/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift b/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift index f8f85248c3cebfefe5125f3ad6a3090c7d499057..650a582f69f5a4823e99160c7a8e5cf3b4c26e32 100644 --- a/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift +++ b/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift @@ -32,11 +32,31 @@ class MaterialTextField: MaterialTextContainer { override var keyboardType: UIKeyboardType { didSet { textField.keyboardType = keyboardType } } - + + override var isSecureTextEntry: Bool { + didSet { textField.isSecureTextEntry = isSecureTextEntry } + } + + var rightView: UIView? { + didSet { textField.rightView = rightView } + } + + var rightViewMode: UITextFieldViewMode = UITextFieldViewMode.never { + didSet { textField.rightViewMode = rightViewMode } + } + var prohibitedOptions: ProhibitedOptions = .none { didSet { textField.prohibitedOptions = prohibitedOptions } } - + + var autocapitalizationType: UITextAutocapitalizationType = UITextAutocapitalizationType.sentences { + didSet { textField.autocapitalizationType = autocapitalizationType } + } + + func setTextFieldFirstResponder() { + textField.becomeFirstResponder() + } + // MARK: - Views private lazy var textField: TextField = { @@ -81,7 +101,11 @@ class MaterialTextField: MaterialTextContainer { } extension MaterialTextField: UITextFieldDelegate { - + + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + return shouldBeginEditing?(self) ?? true + } + func textFieldDidBeginEditing(_ textField: UITextField) { beginEditingText() } diff --git a/Nynja/Library/UI/TextInput/Material/MaterialTextView.swift b/Nynja/Library/UI/TextInput/Material/MaterialTextView.swift index 1f38ace48f4ae43674c8663fb0d60ee523121786..07610c4bd4e0d1fd35eb8bab0445db886eb31058 100644 --- a/Nynja/Library/UI/TextInput/Material/MaterialTextView.swift +++ b/Nynja/Library/UI/TextInput/Material/MaterialTextView.swift @@ -10,76 +10,75 @@ import UIKit import SnapKit class MaterialTextView: MaterialTextContainer { - + // MARK: - MaterialTextInput - + override var text: String { didSet { textView.text = text } } - + override var font: UIFont? { didSet { textView.font = font } } - + override var textColor: UIColor { didSet { textView.textColor = textColor } } - + override var cursorColor: UIColor { didSet { textView.tintColor = cursorColor } } - - + + // MARK: - Views - + private lazy var textView: UITextView = { let textView = UITextView() - textView.font = font textView.backgroundColor = UIColor.nynja.clear textView.tintColor = cursorColor textView.textColor = textColor - + textView.isScrollEnabled = false textView.textContainerInset = .zero textView.textContainer.lineFragmentPadding = 0 - + floatingContainer.inputContainer.addSubview(textView) textView.snp.makeConstraints { make in make.edges.equalToSuperview() } - + return textView }() - - + + // MARK: - Setup override func baseSetup() { super.baseSetup() - + textView.delegate = self textView.accessibilityIdentifier = "material_text_view" } - + } extension MaterialTextView: UITextViewDelegate { - + func textViewDidBeginEditing(_ textView: UITextView) { beginEditingText() } - + func textViewDidEndEditing(_ textView: UITextView) { endEditingText() } - + func textViewDidChange(_ textView: UITextView) { text = textView.text textChanged?(self) } - + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { return shouldTextChanged?(self, range, text) ?? true } - + } diff --git a/Nynja/Modules/Contacts/ContactsProtocols.swift b/Nynja/Modules/Contacts/ContactsProtocols.swift index 67e506d19081948e45d661286b21e8199cbbc034..6e03789be01dd6e9e944ea64e1c7efad6e8b83cd 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 5d089698ff83bc70e304dee39bf68a106b47712e..6dfd48079d56fe23ec9376d0089e1b7cf6bd49d7 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 7e89af39819eb149b2227f303175480945553985..fcd8b231077aeb50d0fca0daaba2787177fa9b36 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 0000000000000000000000000000000000000000..e13f1660911a1925b3f721b360eacdbf2982061a --- /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 ee8a624d086a602ee4fb4d240b29249b2c5482eb..c6d5d2a0ca9905312db5b955a5113ffa1630d668 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 a7f24db2965acaab5a32327d8816252f70e65c08..9cd455cfd216df1822254025857dee2958c9f8ba 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 ba545e5f663b0cc015cda592f0f7b4d658bba5af..3949b1eb52c8ddbeb4e9a4dae9c04cee14602287 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 1a00ec0868aada37f197ad361b1c618766ff9b82..50401f99aa1d637a9c9bd567993d5cb78c511476 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 7774e1b293cf19f1a492e8caf9eb535f21e5b1ab..a71af034b4c1fa6be7cf1d716afb199557e607fe 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 341dd671dd0a43c4a0b6a668fc6674495a4d531f..ab6c474cd3352a0212ec8515a64374f754a0cb71 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 fb4152c9905687069e52932f9088125ec9e4c194..639fe6186de4fb22b69095653eb25776b522e9d1 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 6dc7f88e7b95d6d2f6244e093fe26655a1c47325..771542b31007ed689ba6cc5cf396cd29fcbd0a45 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 e863ea3810cdaa426a23e5702f82f27ea98b4bf0..18789a8a240abd6bbaf7686bf2ce4d13a17695cd 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 3587625127fcb8ca7487ca991d8444e2b1a230b8..e8c23e695e10ae3d4cb1a0f3b0fa9a4fa9c5567b 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 3de18d1187c6d90ada7be4384a98555575e73914..db8d687e5d9a41674039d834ccd6efc3992cf074 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 5079efc7b57082b2e7b1d0ef4c3cd28b50c66cdb..c0392667eb0f880c93e901377bea5e8fcd813c32 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 6761b4f3924f4aca86bbfd16c8bbac7e1bfd7318..656a7e959ab4f3546b2de8d15617dd10e878456b 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 98e8ec1b5f3aee09b8dbc9100b0ecc41f9bfa020..28183a6daa7cd94ddc4937262cb598ed6df1df18 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 fa0a47a8dc38ac34277b7dc06b5992bda436370a..b15d94a3b81b21109da702ce573259fe4ee7e593 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 95bf69fea7a481a4cc7bd58534a1c477318c737b..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..e70c48a9624f3b13fc94632b5fede02134d99555 --- /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 bb78f45308d29edc14e2446356bcc6afa025bbc8..dcebcf6c472fb1469971e730a060d41650ffe0a5 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 ebe0da2e36473a35e54e6ce2c01c2be60f714e66..0000000000000000000000000000000000000000 --- 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 be39f44528c820da63654a8a878544dd8064cec7..0000000000000000000000000000000000000000 --- 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 2b69e3a11cd2cdeedba389f4a201832009742a74..0000000000000000000000000000000000000000 --- 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 0a598579735de34af393334a269e3b4c250f9ecb..0000000000000000000000000000000000000000 --- 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 7fd91be64a4e5d15b817a8ef1ae2483bdfaff80c..0b73d2bbf27c8549af0f788996db16b08311efc1 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 c8490c9302601eae82efee19206fe60e829c2481..aa9f011e727f8624dd475d47a7cd3442ed8cc874 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 8a40ca45178c77bedfc523ba18f139cd723f8346..045570511bde444a5aa7d25a45d4ddd110921327 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 daa4227568f2e27245199c2d48c775b951754212..f956cc96b181b53618acd3435235a77c2345ae3d 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 54a69db1785d218aad3625b8cd72b0388b3339df..713962962c27805b0b7f484403ab04d0d52a2da2 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 0000000000000000000000000000000000000000..9ce9746149e7da8924932ae863b67ac72f62435b --- /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 0000000000000000000000000000000000000000..8e99edfe925aead787961c741329aabb010c0ab3 --- /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 0000000000000000000000000000000000000000..bae9b04f7cc9166d6ffcdb9195ec940e09756f12 --- /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 0000000000000000000000000000000000000000..cbd9056fe0e9e6e23105d232510d0166de51a628 --- /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 0000000000000000000000000000000000000000..03838dce8e44f3d530b183102a844925d6b189c6 --- /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 0000000000000000000000000000000000000000..42af69ada082e19d10ab8baebfb500ebde6b60e5 --- /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 0000000000000000000000000000000000000000..a8604ad8d8ea936a239da51b2bb30f57389ea1a3 --- /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 0000000000000000000000000000000000000000..37fa859431fb1cb1b6eb51cd61e90483d3b0c8a0 --- /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 0000000000000000000000000000000000000000..fe13c7defa732525e3f6170af15dde16013c2b28 --- /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 0000000000000000000000000000000000000000..33674d2d92e170d542e35b896598ad451e9814eb --- /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 0000000000000000000000000000000000000000..5538415bd1b77a857fe794b33ff32a9dab67d08e --- /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 0000000000000000000000000000000000000000..db481843cc512020971fd29100168c18dd4534f2 --- /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 0000000000000000000000000000000000000000..a8503b458bcb384665f61b760e3fc1251aa484ff --- /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 0000000000000000000000000000000000000000..f9c40bbeee9ba6a820eb6c6ea294dab778a743c2 --- /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 9f2e8d02cbd9985bf8d7112f9d89c1c7c0ea75c4..7038c121bb1c226afcd242ba5066b80b647e6ed4 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 0000000000000000000000000000000000000000..91eeedba7a250d58b421afb06527e28a01bab6aa --- /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 beb44859080cbcbd2df64590c0a1f324fb889488..ce0a5dba9345e9c43a9c4980dba620e330eb38d9 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 0000000000000000000000000000000000000000..64e9b5336eb0e2928d9b63e24a711dc46255f297 --- /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 0000000000000000000000000000000000000000..33bcb2fa7eb088252f7949864abe73d99e00321a --- /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 0000000000000000000000000000000000000000..7a0e8dcbaaae740d1daa356c2e4dee8b5c53630f --- /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 0000000000000000000000000000000000000000..6766c2523ff0dfa69de6d2f5047b27af1cc4da92 --- /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 0000000000000000000000000000000000000000..f33a699c6266585f35989b85d10cc763a03e64ed --- /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 0000000000000000000000000000000000000000..d2ddf75c97a75e2567a0754d443f04901edfe378 --- /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 0000000000000000000000000000000000000000..75a2a4aa5d048883c98f93587025d860e0b7d9b7 --- /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 0000000000000000000000000000000000000000..4ee96a649a59ca98dae3cfaa7e84684e279607c4 --- /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 0000000000000000000000000000000000000000..9c9cf41872564dd9eea790d5bbbaca88425bfa70 --- /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 0000000000000000000000000000000000000000..b22d6216ed840cef17513ad245a7b167fa3d39ca --- /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 0000000000000000000000000000000000000000..786f7472b263c491931469c2be9fa5aacb5a9a82 --- /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 0000000000000000000000000000000000000000..86fd8e12b92140a1a2d29bdc020c40bb36ccb113 --- /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 0000000000000000000000000000000000000000..3ca5b0957fe35bae7b824d137329b82cff884b9c --- /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 0000000000000000000000000000000000000000..26a760216ec3f4ec189f2dcdc898cadf17d5c1d8 --- /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 0000000000000000000000000000000000000000..c5d4bb09f422d42c64706bd466175fb81ca04cc1 --- /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 0000000000000000000000000000000000000000..d8c09e9be723d721c307a23a45168f98f8b25fe5 --- /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 0000000000000000000000000000000000000000..01f3df4dbb1e9e9cc3275fad564453c7ba59f5b7 --- /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 0000000000000000000000000000000000000000..ab5405004f5be04fd69eb4b0057ca895c9d06860 --- /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 0000000000000000000000000000000000000000..6e59f871f7cf5f116cf81b3c23d5f0a892ea2ad7 --- /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 0000000000000000000000000000000000000000..aa2cf0bb3bce401bebc2aec00614d511efb34cfc --- /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 0000000000000000000000000000000000000000..651ebca45e81f1c0dc252a16145c6827749e5888 --- /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 0000000000000000000000000000000000000000..8a553fdaa16157a38edcbdf604786a3534cc36ac --- /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 0000000000000000000000000000000000000000..72e075c87ea8e901bcedcf01f22df07b0ac29af8 --- /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 0000000000000000000000000000000000000000..dabc41ae5dc3d8f3e3b706fe74fc14910e760559 --- /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 0000000000000000000000000000000000000000..fab31e0659bfb14a17cef65af48ddee2477f9ed1 --- /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 0000000000000000000000000000000000000000..955a34b5c9af6974dbadcf7b9b5e750e22d7726a --- /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 0000000000000000000000000000000000000000..8c3e5aeb867213f9e60966afa62cb4cf2b718fc9 --- /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 0000000000000000000000000000000000000000..f8ba7366c8fa8b88713bc873f563d25433b1b1cb --- /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 0000000000000000000000000000000000000000..0450aa975592865c5b5af61755da115a0aae0b6b --- /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 0000000000000000000000000000000000000000..fbab66727ff643c14d01329f0107e6f7ccd306ee --- /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 0000000000000000000000000000000000000000..734f33fde59223751399c73cab58d801485ff28a --- /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 0000000000000000000000000000000000000000..7dd2e1db88dd91ecb0621874b8e00dabdaa3315a --- /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 0000000000000000000000000000000000000000..eac31c52fdd7980b059628442df937f6c214bc27 --- /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 0000000000000000000000000000000000000000..f4b04878d2b339af3f73f3b0ac042707439afd3a --- /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 15632774282335bedeeacc48b7163cae31a16d61..6d845c5af7faaed8b44c544d2765d775d394b212 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 0000000000000000000000000000000000000000..b2af629872436d56c36ab9be9ef0fb3c92ecabe8 --- /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 0000000000000000000000000000000000000000..967cd8b402664d88beee9742014cf2ad250500a8 --- /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 20c5418dc0e57e42e9d45681b5478ba08da096c7..0b5913d1b4f8af8d9acd2691d11ca5945fd073f5 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 0000000000000000000000000000000000000000..aad69f760c97a9c7e0196ea079b39c86386e11d4 --- /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 0000000000000000000000000000000000000000..86632d0ffd23672fb6209cf5f155b3699137a009 --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/Interactor/WalletDetailsInteractor.swift @@ -0,0 +1,59 @@ +// +// 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 + } + + func copyAddress() { + UIPasteboard.general.string = input.address + } +} + +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 0000000000000000000000000000000000000000..f5f200670f9c047a0bfdd396b6ddb31903aecc00 --- /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() + return usd.formattedCurrency + } + + 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() + } + + func copyAddress() { + interactor.copyAddress() + } + + //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 0000000000000000000000000000000000000000..a64a8c5f7839a1fd2c47d4879a7018bc1fe624ff --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/View/WalletDetailsViewController.swift @@ -0,0 +1,354 @@ +// +// 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(sendButtonAction), 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.amountHeight, + 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.manatee, + 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 addressTextField: MaterialTextField = { + + let textField = MaterialTextField() + textField.backgroundColor = UIColor.clear + textField.placeholder = "wallet_nynja_adress".localized + textField.isUserInteractionEnabled = true + + let buttonIcon = UIImage.nynja.Messages.ContextMenu.icCopyContextMenu.image + let button = UIButton(type: .custom) + button.frame = CGRect(origin: .zero, size: buttonIcon.size) + button.isUserInteractionEnabled = true + button.setImage(buttonIcon, for: .normal) + button.addTarget(self, action: #selector(copyButtonAction), for: .touchUpInside) + + textField.rightView = button + textField.rightViewMode = UITextFieldViewMode.always + textField.cursorColor = UIColor.nynja.white + textField.textColor = UIColor.nynja.manatee + + view.addSubview(textField) + + textField.snp.makeConstraints { make in + make.top.equalTo(usdEquivalentLabel).offset(Constraints.addressTextField.topOffset) + make.height.equalTo(Constraints.addressTextField.height) + make.left.right.equalToSuperview().inset(Constraints.addressTextField.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 + }() + + private func makeAttributedAmout(for amountString: String) -> NSAttributedString? { + guard + let font = UIFont.makeFont( + with: FontFamily.NotoSans.regular.name, + height: Constraints.nynBalanceLabel.amountHeight), + let currencyFont = UIFont.makeFont( + with: FontFamily.NotoSans.regular.name, + height: Constraints.nynBalanceLabel.currencyHeight)else { + return nil + } + let color = UIColor.nynja.white + let currencyColor = UIColor.nynja.manatee + let attributedString = NSMutableAttributedString(string: amountString, attributes: [.font: font, .foregroundColor: color]) + let currencyRange = (attributedString.string as NSString).range(of: NYN.code) + attributedString.setAttributes([.font: currencyFont, .foregroundColor: currencyColor], range: currencyRange) + return attributedString + } + + private func makeUSDEquivalent(for amountString: String) -> NSAttributedString? { + guard let font = UIFont.makeFont( + with: FontFamily.NotoSans.regular.name, + height: Constraints.nynBalanceLabel.currencyHeight)else { + return nil + } + + let color = UIColor.nynja.manatee + let attributedString = NSMutableAttributedString(string: amountString, attributes: [.font: font, + .foregroundColor: color]) + return attributedString + } + + private func prepareUSDEquivalent(with amountString: String?) -> NSAttributedString? { + guard + let preparedAmount = amountString.map({ "(\($0))" }), + let attributedAmout = makeUSDEquivalent(for: preparedAmount) else { + return nil + } + return attributedAmout + } + + private func prepareBalance(with amountString: String?) -> NSAttributedString? { + guard let amountString = amountString, let attributedAmout = makeAttributedAmout(for: amountString) else { + return nil + } + return attributedAmout + } + + + // MARK: Action + @objc private func historyButtonAction() { + presenter.openTransferHistory() + } + + @objc private func sendButtonAction() { + presenter.openContacts() + } + + @objc private func copyButtonAction() { + presenter.copyAddress() + } + + // 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 + + let amountString = presenter.formattedCurrencyBalance() + nynBalanceLabel.attributedText = prepareBalance(with: amountString) + + let usdEquivalent = presenter.formattedUSDEquivalent() + usdEquivalentLabel.attributedText = prepareUSDEquivalent(with: usdEquivalent) + addressTextField.text = presenter.walletAdress() + + addressTextField.shouldBeginEditing = { (input) in + return false + } + } + + // 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" + addressTextField.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 amountHeight = CGFloat(30.adjustedByWidth) + static let currencyHeight = CGFloat(22.adjustedByWidth) + static let topOffset = CGFloat(8.adjustedByWidth) + static let leftOffset = CGFloat(16.adjustedByWidth) + } + + enum usdEquivalentLabel { + static let height = CGFloat(22.adjustedByWidth) + static let topOffset = CGFloat(8.adjustedByWidth) + static let leftOffset = CGFloat(16.adjustedByWidth) + } + + enum addressTextField { + 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 0000000000000000000000000000000000000000..25878cfd34c3b6bcafc21742b789c7e6ea0da63d --- /dev/null +++ b/Nynja/Modules/Wallet Flows/WalletDetails/WalletDetailsProtocols.swift @@ -0,0 +1,44 @@ +// +// 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 + func copyAddress() +} + +protocol WalletDetailsInteractorOutputProtocol: class { +} + +protocol WalletDetailsInteractorInputProtocol: class { + + func fetchQRCodeImageData(completion: ((Data?) -> Void)) + func getProfile() -> Profile + func getNynjaCoinBalance() -> NYNMoney + func getUSDEquivalent() -> Money + func getWalletAdress() -> String + func copyAddress() +} 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 0000000000000000000000000000000000000000..26795194ef27eb1eba4e470ca125ee2c7653792f --- /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 3ee3ecc236d651b0813e25eb473c0283da418ea6..0000000000000000000000000000000000000000 --- 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 3a59b42903fc55b472f145d111c04871ba1909aa..0000000000000000000000000000000000000000 --- 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 75e1ce817a2d3120c00cb6476f6710db8acef246..0000000000000000000000000000000000000000 --- 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 13136e07f70f8fec37c0bc4a3f8d69828e008890..0000000000000000000000000000000000000000 --- 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 9f8d411d24ef6427ec56ea2f9e3ca033bb4120fe..de1b30c305ebbeb39a7049e5d2e657439cdbeef4 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 Binary files a/Nynja/Resources/Assets.xcassets/Wallet/ic_eos.imageset/ic_eos.pdf and b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_transfer.imageset/ic_arrow_down.pdf differ 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 58ae0c87880ad1ee7b5177c97bb454aa4c6b16d9..aa47f2f79b1edf5a86a7ae6afc0431b7690e227b 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 Binary files a/Nynja/Resources/Assets.xcassets/ic_arrow_right-1.imageset/icons_ic_arrow_right.pdf and b/Nynja/Resources/Assets.xcassets/Wallet/ic_arrow_down_up_transfer.imageset/ic_arrow_down_up.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_context_blockchain.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_blockchain.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..94e9266cb7b535211efd083123c9671077cc18a4 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_blockchain.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_context_blockchain.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_btc.imageset/ic_btc.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_blockchain.imageset/ic_context_blockchain.pdf similarity index 60% rename from Nynja/Resources/Assets.xcassets/Wallet/ic_btc.imageset/ic_btc.pdf rename to Nynja/Resources/Assets.xcassets/Wallet/ic_context_blockchain.imageset/ic_context_blockchain.pdf index 2c75f264cdda97063c22eb495cddccd05f520c9f..7acf601adb4be954ad144e7eaed9db56a19d3789 100644 Binary files a/Nynja/Resources/Assets.xcassets/Wallet/ic_btc.imageset/ic_btc.pdf and b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_blockchain.imageset/ic_context_blockchain.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_context_transfer.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_transfer.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..ff1aaba02679749d4c808586e2d555964225a633 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_transfer.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_context_transfer.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_context_transfer.imageset/ic_context_transfer.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_transfer.imageset/ic_context_transfer.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f1705d284b958476490bb768da69c8b311c0a5bf Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Wallet/ic_context_transfer.imageset/ic_context_transfer.pdf differ 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 8c70e0f7b7670173d99afab2b4b3e48ee47bbaaf..539b493ad0e378f50daba4d7387519acd7393882 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 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Wallet/ic_eth_disabled.imageset/ic_eth_disabled.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_qr_code_wallet.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_qr_code_wallet.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..7e9e2c436ea38ff1c9ed5f1b79c6fde8dce7c6ff --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_qr_code_wallet.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_qr_code_wallet.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_qr_code_wallet.imageset/ic_qr_code_wallet.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_qr_code_wallet.imageset/ic_qr_code_wallet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b3df33908e6d98d3882bef6448d2626f2bb0b08d Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Wallet/ic_qr_code_wallet.imageset/ic_qr_code_wallet.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Wallet/ic_transfer_history_empty.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Wallet/ic_transfer_history_empty.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..f2dfe2e4413cadd5fe3cfe495cb0e2a22443ff8d --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Wallet/ic_transfer_history_empty.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ic_contacts_empty.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_transfer_history_empty.imageset/ic_contacts_empty.pdf b/Nynja/Resources/Assets.xcassets/Wallet/ic_transfer_history_empty.imageset/ic_contacts_empty.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6b00fc0fc1d5aba65f99ba14cf50d502138dd6e1 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Wallet/ic_transfer_history_empty.imageset/ic_contacts_empty.pdf differ 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 0000000000000000000000000000000000000000..3f809c910ad8ee7fab1350785c0867e60a60ac41 --- /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 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Wallet/ic_wallet_absent.imageset/ic_wallet_absent.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/ic_back.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/ic_back.imageset/Contents.json index 1c80cc0ca1d408d2101506f4e65f46051c1f453f..b25b3e8dabafb42e2efb9468f9fa83ca0a231907 100644 --- a/Nynja/Resources/Assets.xcassets/ic_back.imageset/Contents.json +++ b/Nynja/Resources/Assets.xcassets/ic_back.imageset/Contents.json @@ -2,14 +2,20 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_back.pdf" + "filename" : "ic_back.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" - }, - "properties" : { - "preserves-vector-representation" : true } } \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/ic_back.imageset/ic_back.pdf b/Nynja/Resources/Assets.xcassets/ic_back.imageset/ic_back.pdf index 855ef6cd4767fca0937e4bbebc059314323aec9e..5575e0a6cc10b652490cc33651c94bd281e0b257 100644 Binary files a/Nynja/Resources/Assets.xcassets/ic_back.imageset/ic_back.pdf and b/Nynja/Resources/Assets.xcassets/ic_back.imageset/ic_back.pdf differ diff --git a/Nynja/Resources/Constants.swift b/Nynja/Resources/Constants.swift index 275753bc40f40c382c46a03707372b3f162ab69d..766e28ddfbb44be5f2f2492a5586e8ec63983f14 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 91ad8e0886492cf30050eb1bad9397afbbe005aa..c859f9571d7858ffda0f479ca48b27704df579ad 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 ec8f4d2a3682c331f9e61dd3d5a3cd9b1c121507..576c17d9c845c24c4faa82334e46c5de127944a6 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 9fdddc8c612599e19c1c1956700464e6b7f5ffa5..809b4f18a34c867268618dfcb8e9bca1e388e69d 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 f82ec66e826f2637e2c09ec05296a6da20e826ac..57cb974e71fe4780ce2dac584fc5d580a4978830 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 00565d312b714ca7eb523befcb5790185ad0b1ad..fefee8208aedeac6446d2407a9cc4665310ed94c 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 9b4d8929a29d04131b404c3374c615f35b46f1de..14e2ea545e86260f6af4af516bfe39cf22bb016e 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 a790935d3ecaa4632927c8edb5cf997fe25b0d20..762eb86ae64f2817df201eceab3d938cc12851a4 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 8870942b72a32a785eb7e1e0820d806e259f2825..9ef71ae31dc8f7c563b4e79dca10c6b262092e9a 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 0000000000000000000000000000000000000000..53b86c282454369a5b3fca89f9183ed2a933cbc1 --- /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 0000000000000000000000000000000000000000..45633ffb4b29037d9642bc806f8ac8ea887696d7 --- /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 3523fdc72aafc9ba43e6aab8ac8fa04e3f6db76c..4dcc642cbc5b1384d2c40d2b19275dbbcf56ee22 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 4765efb6cd0a43cc010fa38e8139cd3379dd2eea..2530e66d8f34339e06ef5dfa9306cbc1a77a691d 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/Utils/Money/CryptoMoney.swift b/Nynja/Utils/Money/CryptoMoney.swift index 87f837a6fa7dd580a74ee6aa9e2a3f6337741005..870814de0b3ac61d78cb6fef9706df4e0e71628e 100644 --- a/Nynja/Utils/Money/CryptoMoney.swift +++ b/Nynja/Utils/Money/CryptoMoney.swift @@ -8,11 +8,11 @@ typealias NYNMoney = Money -extension Money where Currency: CryptoCurrency { +extension Money { var formattedCurrency: String? { let formatter = CryptoMoneyFormatter.currencyNumberFormatter - formatter.currencyCode = currency.code + formatter.currencySymbol = currency.code formatter.maximumFractionDigits = currency.minorUnit return formatter.string(for: amount) diff --git a/Nynja/Validation/TextInputValidationService.swift b/Nynja/Validation/TextInputValidationService.swift index c9f090947b32d24670717b6bf7433b4d92002163..cabfaa064abb1f510914103adff0adf022661a33 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 0000000000000000000000000000000000000000..1782f25725d2bf006e80624891013f23b129cf72 --- /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 0000000000000000000000000000000000000000..1c1f8f65185da11e298520cfad90a7cba5169baf --- /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 0000000000000000000000000000000000000000..0aa08969641bad8096044ca1a466a2df9431744f --- /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 de4757d7b4833800805a71c4ac3c293a747d2ad3..c6e03d22d1f572ae48713a5deac1d31d3ea5b7d5 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 4504c8bf80b667404625a998c0f94505343e4770..0a9ccddff57463c57d052548365df331e21ab69d 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 fb4bb8775062fbc8b97839bc281e8ef2bef195bc..6130da5b7c12972b5393dfa590fb35017924513b 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