diff --git a/Trust.xcodeproj/project.pbxproj b/Trust.xcodeproj/project.pbxproj index ccf9e6496..e7e82f9a9 100644 --- a/Trust.xcodeproj/project.pbxproj +++ b/Trust.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ 296AF9AB1F7380920058AF78 /* GetTransactionCountRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296AF9AA1F7380920058AF78 /* GetTransactionCountRequest.swift */; }; 2977CAE01F7DEEB0009682A0 /* FakeEtherKeystore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2977CADF1F7DEEB0009682A0 /* FakeEtherKeystore.swift */; }; 297800521F71FDCF003185C1 /* FormAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297800511F71FDCF003185C1 /* FormAppearance.swift */; }; + 2981F4731F8303E600CA6590 /* TransactionCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2981F4721F8303E600CA6590 /* TransactionCoordinatorTests.swift */; }; 29850D251F6B27A800791A49 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29850D241F6B27A800791A49 /* R.generated.swift */; }; 29850D2B1F6B30FF00791A49 /* TransactionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29850D2A1F6B30FF00791A49 /* TransactionViewController.swift */; }; 2996F1431F6C96FF005C33AE /* ImportWalletViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2996F1421F6C96FF005C33AE /* ImportWalletViewModel.swift */; }; @@ -95,7 +96,7 @@ 29BBB36B1F7BCEDD006BC91B /* GethBigInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BBB36A1F7BCEDD006BC91B /* GethBigInt.swift */; }; 29BE3FCE1F706D8800F6BFC2 /* EthereumConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FCD1F706D8800F6BFC2 /* EthereumConverterTests.swift */; }; 29BE3FD01F7071A200F6BFC2 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FCF1F7071A200F6BFC2 /* UIColor.swift */; }; - 29BE3FD21F707DC300F6BFC2 /* TransactionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FD11F707DC300F6BFC2 /* TransactionCoordinator.swift */; }; + 29BE3FD21F707DC300F6BFC2 /* TransactionDataCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29BE3FD11F707DC300F6BFC2 /* TransactionDataCoordinator.swift */; }; 29C9F5F91F720BD30025C494 /* FloatLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C9F5F81F720BD30025C494 /* FloatLabelCell.swift */; }; 29C9F5FB1F720C050025C494 /* FloatLabelTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C9F5FA1F720C050025C494 /* FloatLabelTextField.swift */; }; 29CA4B771F6FBBFB0032313D /* RequestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CA4B761F6FBBFB0032313D /* RequestViewModel.swift */; }; @@ -112,6 +113,10 @@ 29E2E33E1F7A2423000CF94A /* TransactionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2E33D1F7A2423000CF94A /* TransactionHeaderView.swift */; }; 29E2E3411F7B1585000CF94A /* ActionButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E2E3401F7B1585000CF94A /* ActionButtonRow.swift */; }; 29EB102A1F6CBD23000907A4 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29EB10291F6CBD23000907A4 /* UIAlertController.swift */; }; + 29FC0CB11F81CED10036089F /* TransactionByHashRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB01F81CED10036089F /* TransactionByHashRequest.swift */; }; + 29FC0CB31F81D2640036089F /* TransactionReceiptRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB21F81D2640036089F /* TransactionReceiptRequest.swift */; }; + 29FC0CB61F8298820036089F /* TransactionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB51F8298820036089F /* TransactionCoordinator.swift */; }; + 29FC0CB81F8299510036089F /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FC0CB71F8299510036089F /* Coordinator.swift */; }; 29FF12F61F74799D00AFD326 /* NSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF12F51F74799D00AFD326 /* NSAttributedString.swift */; }; 29FF12F81F747D6C00AFD326 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF12F71F747D6C00AFD326 /* Error.swift */; }; 29FF12FB1F74CC8200AFD326 /* EthereumAddressRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF12FA1F74CC8200AFD326 /* EthereumAddressRule.swift */; }; @@ -214,6 +219,7 @@ 296AF9AA1F7380920058AF78 /* GetTransactionCountRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetTransactionCountRequest.swift; sourceTree = ""; }; 2977CADF1F7DEEB0009682A0 /* FakeEtherKeystore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeEtherKeystore.swift; sourceTree = ""; }; 297800511F71FDCF003185C1 /* FormAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormAppearance.swift; sourceTree = ""; }; + 2981F4721F8303E600CA6590 /* TransactionCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCoordinatorTests.swift; sourceTree = ""; }; 29850D241F6B27A800791A49 /* R.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = R.generated.swift; sourceTree = ""; }; 29850D2A1F6B30FF00791A49 /* TransactionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewController.swift; sourceTree = ""; }; 2996F1421F6C96FF005C33AE /* ImportWalletViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportWalletViewModel.swift; sourceTree = ""; }; @@ -237,7 +243,7 @@ 29BBB36A1F7BCEDD006BC91B /* GethBigInt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GethBigInt.swift; sourceTree = ""; }; 29BE3FCD1F706D8800F6BFC2 /* EthereumConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumConverterTests.swift; sourceTree = ""; }; 29BE3FCF1F7071A200F6BFC2 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; - 29BE3FD11F707DC300F6BFC2 /* TransactionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCoordinator.swift; sourceTree = ""; }; + 29BE3FD11F707DC300F6BFC2 /* TransactionDataCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDataCoordinator.swift; sourceTree = ""; }; 29C9F5F81F720BD30025C494 /* FloatLabelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatLabelCell.swift; sourceTree = ""; }; 29C9F5FA1F720C050025C494 /* FloatLabelTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatLabelTextField.swift; sourceTree = ""; }; 29CA4B761F6FBBFB0032313D /* RequestViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestViewModel.swift; sourceTree = ""; }; @@ -254,6 +260,10 @@ 29E2E33D1F7A2423000CF94A /* TransactionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHeaderView.swift; sourceTree = ""; }; 29E2E3401F7B1585000CF94A /* ActionButtonRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonRow.swift; sourceTree = ""; }; 29EB10291F6CBD23000907A4 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = ""; }; + 29FC0CB01F81CED10036089F /* TransactionByHashRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionByHashRequest.swift; sourceTree = ""; }; + 29FC0CB21F81D2640036089F /* TransactionReceiptRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionReceiptRequest.swift; sourceTree = ""; }; + 29FC0CB51F8298820036089F /* TransactionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCoordinator.swift; sourceTree = ""; }; + 29FC0CB71F8299510036089F /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; 29FF12F51F74799D00AFD326 /* NSAttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedString.swift; sourceTree = ""; }; 29FF12F71F747D6C00AFD326 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 29FF12FA1F74CC8200AFD326 /* EthereumAddressRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumAddressRule.swift; sourceTree = ""; }; @@ -389,11 +399,11 @@ 2912CD291F6A831D00C6CBE3 /* Transactions */ = { isa = PBXGroup; children = ( + 2977CAE31F7E0B3F009682A0 /* Coordinators */, 29E14FCF1F7F456A00185568 /* Storage */, 2977CAED1F7E0BEC009682A0 /* Views */, 2977CAEC1F7E0BE2009682A0 /* ViewControllers */, 2977CAEB1F7E0BC3009682A0 /* ViewModels */, - 2977CAE31F7E0B3F009682A0 /* Coordinators */, 29B6AECF1F7C881100EC6DE3 /* Types */, ); path = Transactions; @@ -429,6 +439,7 @@ 297800511F71FDCF003185C1 /* FormAppearance.swift */, 29C9F5F81F720BD30025C494 /* FloatLabelCell.swift */, 29C9F5FA1F720C050025C494 /* FloatLabelTextField.swift */, + 29FC0CB71F8299510036089F /* Coordinator.swift */, ); path = UI; sourceTree = ""; @@ -468,6 +479,8 @@ 296AF9A81F737F6F0058AF78 /* SendRawTransactionRequest.swift */, 296AF9AA1F7380920058AF78 /* GetTransactionCountRequest.swift */, 290FD8BA1F7BFE7400548896 /* GetBlockByNumberRequest.swift */, + 29FC0CB01F81CED10036089F /* TransactionByHashRequest.swift */, + 29FC0CB21F81D2640036089F /* TransactionReceiptRequest.swift */, ); path = Requests; sourceTree = ""; @@ -563,7 +576,8 @@ 2977CAE31F7E0B3F009682A0 /* Coordinators */ = { isa = PBXGroup; children = ( - 29BE3FD11F707DC300F6BFC2 /* TransactionCoordinator.swift */, + 29BE3FD11F707DC300F6BFC2 /* TransactionDataCoordinator.swift */, + 29FC0CB51F8298820036089F /* TransactionCoordinator.swift */, ); path = Coordinators; sourceTree = ""; @@ -856,6 +870,7 @@ children = ( 29FF13071F75F0AE00AFD326 /* AppCoordinatorTests.swift */, 296106CB1F776FD00006164B /* WalletCoordinatorTests.swift */, + 2981F4721F8303E600CA6590 /* TransactionCoordinatorTests.swift */, ); path = Coordinators; sourceTree = ""; @@ -1236,6 +1251,7 @@ 291F52B11F6B814300B369AB /* MG Basic Math.swift in Sources */, 29E2E33A1F7A008C000CF94A /* UIView.swift in Sources */, 291F52B21F6B814300B369AB /* MG Benchmark Tools.swift in Sources */, + 29FC0CB11F81CED10036089F /* TransactionByHashRequest.swift in Sources */, 291ED08F1F6F613200E7E93A /* GetTransactionRequest.swift in Sources */, 295A59381F71C1B90092F0FC /* AccountsCoordinator.swift in Sources */, 296421971F70C1F200EB363B /* ErrorView.swift in Sources */, @@ -1250,10 +1266,11 @@ 296106C81F7646590006164B /* TokensViewModel.swift in Sources */, 29E2E3411F7B1585000CF94A /* ActionButtonRow.swift in Sources */, 291F52B41F6B814300B369AB /* SMP Core.swift in Sources */, + 29FC0CB61F8298820036089F /* TransactionCoordinator.swift in Sources */, 29B6AECB1F7C5FA900EC6DE3 /* SendAndRequestViewContainer.swift in Sources */, 2996F14A1F6C9D10005C33AE /* ExportCoordinator.swift in Sources */, 291D73C61F7F500D00A8AB56 /* TransactionItemState.swift in Sources */, - 29BE3FD21F707DC300F6BFC2 /* TransactionCoordinator.swift in Sources */, + 29BE3FD21F707DC300F6BFC2 /* TransactionDataCoordinator.swift in Sources */, 291F52B51F6B814300B369AB /* SMP String Module.swift in Sources */, 291ED0921F6FA5D900E7E93A /* RequestViewController.swift in Sources */, 296106C21F76403A0006164B /* TokenViewCell.swift in Sources */, @@ -1262,6 +1279,7 @@ 296106BF1F7639250006164B /* FetchTransactionsRequest.swift in Sources */, 293B8B451F70A20200356286 /* TransactionViewCell.swift in Sources */, 293E66421F8026F10052973D /* Block.swift in Sources */, + 29FC0CB81F8299510036089F /* Coordinator.swift in Sources */, 296106C61F7645CC0006164B /* TokensViewController.swift in Sources */, 29282B531F7630970067F88D /* Token.swift in Sources */, 29C9F5FB1F720C050025C494 /* FloatLabelTextField.swift in Sources */, @@ -1282,6 +1300,7 @@ 29CA4B791F6FBFD50032313D /* Balance.swift in Sources */, 291F52A71F6B766100B369AB /* BalanceRequest.swift in Sources */, 29D72A2A1F6A8D1500CE9209 /* AppCoordinator.swift in Sources */, + 29FC0CB31F81D2640036089F /* TransactionReceiptRequest.swift in Sources */, 29A13E331F6B1B7A00E432A2 /* AppStyle.swift in Sources */, 29FF12F81F747D6C00AFD326 /* Error.swift in Sources */, 29282B581F7636840067F88D /* GetTokensRequest.swift in Sources */, @@ -1345,6 +1364,7 @@ 2977CAE01F7DEEB0009682A0 /* FakeEtherKeystore.swift in Sources */, 29E14FD51F7F470C00185568 /* TransactionsStorageTests.swift in Sources */, 29FF130A1F75F67200AFD326 /* Address.swift in Sources */, + 2981F4731F8303E600CA6590 /* TransactionCoordinatorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Trust/AppCoordinator.swift b/Trust/AppCoordinator.swift index 7f68c64df..bf4ff88ef 100644 --- a/Trust/AppCoordinator.swift +++ b/Trust/AppCoordinator.swift @@ -3,7 +3,7 @@ import Foundation import UIKit -class AppCoordinator: NSObject { +class AppCoordinator: NSObject, Coordinator { let rootNavigationController: UINavigationController @@ -17,16 +17,10 @@ class AppCoordinator: NSObject { return WalletCoordinator(rootNavigationController: self.rootNavigationController) }() - lazy var settingsCoordinator: SettingsCoordinator = { - return SettingsCoordinator(navigationController: self.rootNavigationController) - }() - - lazy var accountsCoordinator: AccountsCoordinator = { - return AccountsCoordinator(navigationController: self.rootNavigationController) - }() - private var keystore: Keystore + var coordinators: [Coordinator] = [] + init( window: UIWindow, keystore: Keystore = EtherKeystore(), @@ -48,8 +42,14 @@ class AppCoordinator: NSObject { } func showTransactions(for account: Account) { - let controller = makeTransactionsController(with: account) - rootNavigationController.viewControllers = [controller] + let coordinator = TransactionCoordinator( + account: account, + rootNavigationController: rootNavigationController + ) + coordinator.delegate = self + rootNavigationController.viewControllers = [coordinator.rootViewController] + addCoordinator(coordinator) + keystore.recentlyUsedAccount = account } @@ -58,36 +58,10 @@ class AppCoordinator: NSObject { walletCoordinator.delegate = self } - private func makeTransactionsController(with account: Account) -> TransactionsViewController { - let controller = TransactionsViewController(account: account) - controller.navigationItem.leftBarButtonItem = UIBarButtonItem(image: R.image.settings_icon(), landscapeImagePhone: R.image.settings_icon(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showSettings)) - controller.navigationItem.rightBarButtonItem = UIBarButtonItem(image: R.image.accountsSwitch(), landscapeImagePhone: R.image.accountsSwitch(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showAccounts)) - controller.delegate = self - return controller - } - - @objc func dismiss() { - rootNavigationController.dismiss(animated: true, completion: nil) - } - @objc func reset() { + coordinators.removeAll() rootNavigationController.viewControllers = [welcomeViewController] } - - @objc func showAccounts() { - accountsCoordinator.start() - accountsCoordinator.delegate = self - } - - @objc func showSettings() { - settingsCoordinator.start() - settingsCoordinator.delegate = self - } - - func showTokens(for account: Account) { - let controller = TokensViewController(account: account) - rootNavigationController.pushViewController(controller, animated: true) - } } extension AppCoordinator: WelcomeViewControllerDelegate { @@ -96,9 +70,15 @@ extension AppCoordinator: WelcomeViewControllerDelegate { } } -extension AppCoordinator: SettingsCoordinatorDelegate { - func didCancel(in coordinator: SettingsCoordinator) { +extension AppCoordinator: TransactionCoordinatorDelegate { + func didCancel(in coordinator: TransactionCoordinator) { + removeCoordinator(coordinator) + reset() + } + + func didChangeAccount(to account: Account, in coordinator: TransactionCoordinator) { coordinator.navigationController.dismiss(animated: true, completion: nil) + showTransactions(for: account) } } @@ -116,46 +96,3 @@ extension AppCoordinator: WalletCoordinatorDelegate { coordinator.navigationViewController.dismiss(animated: true, completion: nil) } } - -extension AppCoordinator: AccountsCoordinatorDelegate { - func didCancel(in coordinator: AccountsCoordinator) { - coordinator.navigationController.dismiss(animated: true, completion: nil) - } - - func didSelectAccount(account: Account, in coordinator: AccountsCoordinator) { - showTransactions(for: account) - coordinator.navigationController.dismiss(animated: true, completion: nil) - } - - func didDeleteAccount(account: Account, in coordinator: AccountsCoordinator) { - guard !coordinator.accountsViewController.hasAccounts else { return } - coordinator.navigationController.dismiss(animated: true, completion: nil) - rootNavigationController.dismiss(animated: true, completion: nil) - reset() - } -} - -extension AppCoordinator: TransactionsViewControllerDelegate { - func didPressSend(for account: Account, in viewController: TransactionsViewController) { - let controller = SendAndRequestViewContainer(flow: .send, account: account) - let nav = NavigationController(rootViewController: controller) - controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss)) - rootNavigationController.present(nav, animated: true, completion: nil) - } - - func didPressRequest(for account: Account, in viewController: TransactionsViewController) { - let controller = SendAndRequestViewContainer(flow: .request, account: account) - let nav = NavigationController(rootViewController: controller) - controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss)) - rootNavigationController.present(nav, animated: true, completion: nil) - } - - func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) { - let controller = TransactionViewController(transaction: transaction) - rootNavigationController.pushViewController(controller, animated: true) - } - - func didPressTokens(for account: Account, in viewController: TransactionsViewController) { - showTokens(for: account) - } -} diff --git a/Trust/AppDelegate.swift b/Trust/AppDelegate.swift index 6be255c2f..e4520f530 100644 --- a/Trust/AppDelegate.swift +++ b/Trust/AppDelegate.swift @@ -13,7 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele self.window = window let config = Realm.Configuration( - schemaVersion: 3, + schemaVersion: 4, migrationBlock: { _, _ in } ) Realm.Configuration.defaultConfiguration = config diff --git a/Trust/EtherClient/EtherKeystore.swift b/Trust/EtherClient/EtherKeystore.swift index 4a0de11c7..72d4fe191 100644 --- a/Trust/EtherClient/EtherKeystore.swift +++ b/Trust/EtherClient/EtherKeystore.swift @@ -151,7 +151,7 @@ class EtherKeystore: Keystore { } func getGethAccount(for address: Address) -> GethAccount { - return gethAccounts.filter { $0.getAddress().getHex() == address.address }.first! + return gethAccounts.filter { Address(address: $0.getAddress().getHex()) == address }.first! } } diff --git a/Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift b/Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift index 3483b407e..d9683d6b0 100644 --- a/Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift +++ b/Trust/EtherClient/Etherscan/FetchTransactionsRequest.swift @@ -4,9 +4,17 @@ import Foundation import APIKit struct FetchTransactionsRequest: APIKit.Request { - typealias Response = [ParsedTransaction] + typealias Response = [EtherScanTransaction] let address: String + let startBlock: String + let endBlock: String + + init(address: String, startBlock: String = "0", endBlock: String = "99999999") { + self.address = address + self.startBlock = startBlock + self.endBlock = endBlock + } var baseURL: URL { let config = Config() @@ -26,8 +34,8 @@ struct FetchTransactionsRequest: APIKit.Request { "module": "account", "action": "txlist", "address": address, - "startblock": "0", - "endblock": "99999999", + "startblock": startBlock, + "endblock": endBlock, "sort": "asc", "apikey": "7V8AMAVQWKNAZHZG8ARYB9SQWWKBBDA7S8", ] @@ -37,8 +45,63 @@ struct FetchTransactionsRequest: APIKit.Request { if let objectJSON = object as? [String: AnyObject], let transactionJSON = objectJSON["result"] as? [[String: AnyObject]] { - return transactionJSON.map { .from(address: address.lowercased(), json: $0) } + return transactionJSON.map { .from(json: $0) } } return [] } } + +struct EtherScanTransaction { + let blockHash: String + let blockNumber: String + let transactionIndex: String + let confirmations: String + let cumulativeGasUsed: String + let from: String + let to: String + let gas: String + let gasPrice: String + let gasUsed: String + let hash: String + let value: String + let nonce: String + let timestamp: String + let isError: Bool +} + +extension EtherScanTransaction { + static func from(json: [String: AnyObject]) -> EtherScanTransaction { + let blockHash = json["blockHash"] as? String ?? "" + let blockNumber = json["blockNumber"] as? String ?? "" + let transactionIndex = json["transactionIndex"] as? String ?? "" + let confirmation = json["confirmations"] as? String ?? "" + let cumulativeGasUsed = json["cumulativeGasUsed"] as? String ?? "" + let from = json["from"] as? String ?? "" + let to = json["to"] as? String ?? "" + let gas = json["gas"] as? String ?? "" + let gasPrice = json["gasPrice"] as? String ?? "" + let gasUsed = json["gasUsed"] as? String ?? "" + let hash = json["hash"] as? String ?? "" + let isError = Bool(json["isError"] as? String ?? "") ?? false + let timestamp = (json["timeStamp"] as? String ?? "") + let value = (json["value"] as? String ?? "") + let nonce = (json["nonce"] as? String ?? "") + return EtherScanTransaction( + blockHash: blockHash, + blockNumber: blockNumber, + transactionIndex: transactionIndex, + confirmations: confirmation, + cumulativeGasUsed: cumulativeGasUsed, + from: from, + to: to, + gas: gas, + gasPrice: gasPrice, + gasUsed: gasUsed, + hash: hash, + value: value, + nonce: nonce, + timestamp: timestamp, + isError: isError + ) + } +} diff --git a/Trust/EtherClient/Requests/TransactionByHashRequest.swift b/Trust/EtherClient/Requests/TransactionByHashRequest.swift new file mode 100644 index 000000000..9bc4cf6ec --- /dev/null +++ b/Trust/EtherClient/Requests/TransactionByHashRequest.swift @@ -0,0 +1,28 @@ +// Copyright SIX DAY LLC. All rights reserved. + +import Foundation +import JSONRPCKit + +struct TransactionByHashRequest: JSONRPCKit.Request { + typealias Response = ParsedTransaction + + let hash: String + + var method: String { + return "eth_getTransactionByHash" + } + + var parameters: Any? { + return [ + hash, + ] + } + + func response(from resultObject: Any) throws -> Response { + if let response = resultObject as? [String: AnyObject] { + return ParsedTransaction.from(json: response) + } else { + throw CastError(actualValue: resultObject, expectedType: Response.self) + } + } +} diff --git a/Trust/EtherClient/Requests/TransactionReceiptRequest.swift b/Trust/EtherClient/Requests/TransactionReceiptRequest.swift new file mode 100644 index 000000000..b9bf5dc36 --- /dev/null +++ b/Trust/EtherClient/Requests/TransactionReceiptRequest.swift @@ -0,0 +1,28 @@ +// Copyright SIX DAY LLC. All rights reserved. + +import Foundation +import JSONRPCKit + +struct TransactionReceiptRequest: JSONRPCKit.Request { + typealias Response = ParsedTransaction + + let hash: String + + var method: String { + return "eth_getTransactionReceipt" + } + + var parameters: Any? { + return [ + hash, + ] + } + + func response(from resultObject: Any) throws -> Response { + if let response = resultObject as? [String: AnyObject] { + return ParsedTransaction.from(json: response) + } else { + throw CastError(actualValue: resultObject, expectedType: Response.self) + } + } +} diff --git a/Trust/Models/Address.swift b/Trust/Models/Address.swift index 3181aabea..ae7e9dc15 100644 --- a/Trust/Models/Address.swift +++ b/Trust/Models/Address.swift @@ -7,6 +7,12 @@ struct Address { let address: String init(address: String) { - self.address = address + self.address = address.lowercased() + } +} + +extension Address: Equatable { + static func == (lhs: Address, rhs: Address) -> Bool { + return lhs.address == rhs.address } } diff --git a/Trust/Models/Balance.swift b/Trust/Models/Balance.swift index 762d6188b..43eda9cb3 100644 --- a/Trust/Models/Balance.swift +++ b/Trust/Models/Balance.swift @@ -25,6 +25,9 @@ struct Balance { extension String { var drop0x: String { - return String(self.characters.dropFirst(2)) + if self.count > 2 && self.substring(with: 0..<2) == "0x" { + return String(self.characters.dropFirst(2)) + } + return self } } diff --git a/Trust/Models/Block.swift b/Trust/Models/Block.swift index 2599cb01c..8dbbe900c 100644 --- a/Trust/Models/Block.swift +++ b/Trust/Models/Block.swift @@ -2,8 +2,14 @@ import Foundation +struct ParsedBlock { + let number: String + let timestamp: String +} + struct Block { let number: String + let timestamp: String let transactions: [ParsedTransaction] } @@ -11,9 +17,20 @@ extension Block { static func from(json: [String: AnyObject]) -> Block? { if let transactionsJSON = json["transactions"] as? [[String: AnyObject]] { - let transactions: [ParsedTransaction] = transactionsJSON.map({ .from(json: $0) }) + + let number = json["number"] as? String ?? "" + let timestamp = json["timestamp"] as? String ?? "" + + let block = ParsedBlock( + number: number, + timestamp: timestamp + ) + + let transactions: [ParsedTransaction] = transactionsJSON.map {.from(block: block, transaction: $0)} + return Block( - number: "1", + number: number, + timestamp: timestamp, transactions: transactions ) } diff --git a/Trust/Models/ParsedTransaction.swift b/Trust/Models/ParsedTransaction.swift index 5a7bf3233..d4d3f84c3 100644 --- a/Trust/Models/ParsedTransaction.swift +++ b/Trust/Models/ParsedTransaction.swift @@ -8,14 +8,13 @@ enum TransactionDirection { } struct ParsedTransaction { - let blockHash: String let blockNumber: String + let transactionIndex: String let confirmations: String let cumulativeGasUsed: String let from: String let to: String - let owner: String let gas: String let gasPrice: String let gasUsed: String @@ -24,16 +23,13 @@ struct ParsedTransaction { let nonce: String let timestamp: String let isError: Bool - - var time: Date { - return NSDate(timeIntervalSince1970: TimeInterval(timestamp) ?? 0) as Date - } } extension ParsedTransaction { - static func from(address: String = "", json: [String: AnyObject]) -> ParsedTransaction { + static func from(json: [String: AnyObject]) -> ParsedTransaction { let blockHash = json["blockHash"] as? String ?? "" let blockNumber = json["blockNumber"] as? String ?? "" + let transactionIndex = json["transactionIndex"] as? String ?? "" let confirmation = json["confirmations"] as? String ?? "" let cumulativeGasUsed = json["cumulativeGasUsed"] as? String ?? "" let from = json["from"] as? String ?? "" @@ -49,11 +45,11 @@ extension ParsedTransaction { return ParsedTransaction( blockHash: blockHash, blockNumber: blockNumber, + transactionIndex: transactionIndex, confirmations: confirmation, cumulativeGasUsed: cumulativeGasUsed, from: from, to: to, - owner: address, gas: gas, gasPrice: gasPrice, gasUsed: gasUsed, @@ -65,3 +61,40 @@ extension ParsedTransaction { ) } } + +extension ParsedTransaction { + static func from(block: ParsedBlock, transaction: [String: AnyObject]) -> ParsedTransaction { + let blockHash = transaction["blockHash"] as? String ?? "" + let blockNumber = transaction["blockNumber"] as? String ?? "" + let transactionIndex = transaction["transactionIndex"] as? String ?? "0" + let confirmation = transaction["confirmations"] as? String ?? "0" + let cumulativeGasUsed = transaction["cumulativeGasUsed"] as? String ?? "0" + let from = transaction["from"] as? String ?? "" + let to = transaction["to"] as? String ?? "" + let gas = transaction["gas"] as? String ?? "0" + let gasPrice = transaction["gasPrice"] as? String ?? "0" + let gasUsed = transaction["gasUsed"] as? String ?? "0" + let hash = transaction["hash"] as? String ?? "" + let isError = Bool(transaction["isError"] as? String ?? "") ?? false + let timestamp = block.timestamp + let value = transaction["value"] as? String ?? "0" + let nonce = transaction["nonce"] as? String ?? "0" + return ParsedTransaction( + blockHash: blockHash, + blockNumber: BInt(hex: blockNumber.drop0x).dec, + transactionIndex: BInt(hex: transactionIndex.drop0x).dec, + confirmations: confirmation, + cumulativeGasUsed: BInt(hex: cumulativeGasUsed.drop0x).dec, + from: from, + to: to, + gas: BInt(hex: gas.drop0x).dec, + gasPrice: BInt(hex: gasPrice.drop0x).dec, + gasUsed: BInt(hex: gasUsed.drop0x).dec, + hash: hash, + value: BInt(hex: value.drop0x).dec, + nonce: BInt(hex: nonce.drop0x).dec, + timestamp: BInt(hex: timestamp.drop0x).dec, + isError: isError + ) + } +} diff --git a/Trust/Tokens/Types/TokensDataStore.swift b/Trust/Tokens/Types/TokensDataStore.swift index a4e18ab55..413910966 100644 --- a/Trust/Tokens/Types/TokensDataStore.swift +++ b/Trust/Tokens/Types/TokensDataStore.swift @@ -4,6 +4,10 @@ import Foundation import Result import APIKit +enum TokenError: Error { + case failedToFetch +} + protocol TokensDataStoreDelegate: class { func didUpdate(result: Result) } diff --git a/Trust/Transactions/Coordinators/TransactionCoordinator.swift b/Trust/Transactions/Coordinators/TransactionCoordinator.swift index 67f93fdd8..c4e4ccbf5 100644 --- a/Trust/Transactions/Coordinators/TransactionCoordinator.swift +++ b/Trust/Transactions/Coordinators/TransactionCoordinator.swift @@ -2,165 +2,133 @@ import Foundation import UIKit -import JSONRPCKit -import APIKit -import RealmSwift import Result -enum TransactionError: Error { - case failedToFetch -} - -enum TokenError: Error { - case failedToFetch -} - protocol TransactionCoordinatorDelegate: class { - func didUpdate(result: Result<[Transaction], TransactionError>) + func didCancel(in coordinator: TransactionCoordinator) + func didChangeAccount(to account: Account, in coordinator: TransactionCoordinator) } -class TransactionCoordinator { +class TransactionCoordinator: Coordinator { - let storage = TransactionsStorage() - let account: Account + private let keystore: Keystore - var viewModel: TransactionsViewModel { - return .init(transactions: self.storage.objects) - } + lazy var rootViewController: TransactionsViewController = { + let controller = self.makeTransactionsController(with: self.account) + return controller + }() + + lazy var dataCoordinator: TransactionDataCoordinator = { + let coordinator = TransactionDataCoordinator(account: self.account) + return coordinator + }() weak var delegate: TransactionCoordinatorDelegate? - var timer: Timer? - //let notificationToken: NotificationToken - init(account: Account) { + lazy var settingsCoordinator: SettingsCoordinator = { + return SettingsCoordinator(navigationController: self.navigationController) + }() + + lazy var accountsCoordinator: AccountsCoordinator = { + return AccountsCoordinator(navigationController: self.navigationController) + }() + + let account: Account + let navigationController: UINavigationController + var coordinators: [Coordinator] = [] + + init( + account: Account, + rootNavigationController: UINavigationController + ) { self.account = account + self.keystore = EtherKeystore() + self.navigationController = rootNavigationController + } - storage.deleteAll() + private func makeTransactionsController(with account: Account) -> TransactionsViewController { + let controller = TransactionsViewController(account: account, dataCoordinator: dataCoordinator) + controller.navigationItem.leftBarButtonItem = UIBarButtonItem(image: R.image.settings_icon(), landscapeImagePhone: R.image.settings_icon(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showSettings)) + controller.navigationItem.rightBarButtonItem = UIBarButtonItem(image: R.image.accountsSwitch(), landscapeImagePhone: R.image.accountsSwitch(), style: UIBarButtonItemStyle.done, target: self, action: #selector(showAccounts)) + controller.delegate = self + return controller + } -// notificationToken = storage.realm.addNotificationBlock { item, _ in -// switch item { -// case .didChange: break -// case .refreshRequired: break -// } -// } + @objc func showAccounts() { + accountsCoordinator.start() + accountsCoordinator.delegate = self + } + @objc func showSettings() { + settingsCoordinator.start() + settingsCoordinator.delegate = self } - func start() { - fetchTransactions() - fetchPendingTransactions() - timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.timerTrigger), userInfo: nil, repeats: true) + func showTokens(for account: Account) { + let controller = TokensViewController(account: account) + navigationController.pushViewController(controller, animated: true) } - func fetch() { - fetchPendingTransactions() + @objc func dismiss() { + navigationController.dismiss(animated: true, completion: nil) } +} - func fetchTransactions() { - let request = FetchTransactionsRequest(address: account.address.address) - Session.send(request) { result in - switch result { - case .success(let response): - self.update(owner: self.account.address, items: response) - case .failure: - break - } - } +extension TransactionCoordinator: SettingsCoordinatorDelegate { + func didCancel(in coordinator: SettingsCoordinator) { + coordinator.navigationController.dismiss(animated: true, completion: nil) } +} - func fetchPendingTransactions() { - Session.send(EtherServiceRequest(batch: BatchFactory().create(GetBlockByNumberRequest(block: "pending")))) { result in - switch result { - case .success(let block): - //NSLog("transactions \(block)") - for item in block.transactions { - NSLog("item \(item.hash)") - if item.to == self.account.address.address.lowercased() || item.from == self.account.address.address.lowercased() { - self.update(owner: self.account.address, items: [item]) - } else { - self.update(owner: self.account.address, items: []) - } - } - case .failure: - break - } - } +extension TransactionCoordinator: TransactionsViewControllerDelegate { + func didPressSend(for account: Account, in viewController: TransactionsViewController) { + let controller = SendAndRequestViewContainer(flow: .send, account: account) + let nav = NavigationController(rootViewController: controller) + controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss)) + navigationController.present(nav, animated: true, completion: nil) } - @objc func timerTrigger() { - fetchPendingTransactions() + + func didPressRequest(for account: Account, in viewController: TransactionsViewController) { + let controller = SendAndRequestViewContainer(flow: .request, account: account) + let nav = NavigationController(rootViewController: controller) + controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss)) + navigationController.present(nav, animated: true, completion: nil) } - func update(owner: Address, items: [ParsedTransaction]) { - let transactionItems: [Transaction] = items.map { .from(owner: owner, transaction: $0) } - storage.add(transactionItems) - print() + func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) { + let controller = TransactionViewController( + transaction: transaction + ) + navigationController.pushViewController(controller, animated: true) + } - delegate?.didUpdate(result: .success(self.storage.objects)) + func didPressTokens(for account: Account, in viewController: TransactionsViewController) { + showTokens(for: account) } - func print() { - NSLog("objects \(storage.count)") + func reset() { + clean() + delegate?.didCancel(in: self) } - func stop() { - //notificationToken.stop() + func clean() { + dataCoordinator.storage.deleteAll() } } -extension Transaction { - static func from(owner: Address, transaction: ParsedTransaction) -> Transaction { - let state: TransactionState = { - return .pending - }() - - return Transaction( - id: transaction.hash, - owner: owner.address, //TODO - state: state, - blockNumber: transaction.blockNumber, - from: transaction.from, - to: transaction.to, - value: transaction.value, //TODO - gas: transaction.gas, - gasPrice: transaction.gasPrice, - gasUsed: transaction.gasUsed, - confirmations: Int64(transaction.confirmations) ?? 0, - nonce: "0", // TODO - date: NSDate(timeIntervalSince1970: TimeInterval(transaction.timestamp) ?? 0) as Date - ) +extension TransactionCoordinator: AccountsCoordinatorDelegate { + func didCancel(in coordinator: AccountsCoordinator) { + coordinator.navigationController.dismiss(animated: true, completion: nil) } -// static func from(address: String, json: [String: AnyObject]) -> Transaction { -// let blockHash = json["blockHash"] as? String ?? "" -// let blockNumber = json["blockNumber"] as? String ?? "" -// let confirmation = json["confirmations"] as? String ?? "" -// let cumulativeGasUsed = json["cumulativeGasUsed"] as? String ?? "" -// let from = json["from"] as? String ?? "" -// let to = json["to"] as? String ?? "" -// let gas = json["gas"] as? String ?? "" -// let gasPrice = json["gasPrice"] as? String ?? "" -// let gasUsed = json["gasUsed"] as? String ?? "" -// let hash = json["hash"] as? String ?? "" -// let isError = Bool(json["isError"] as? String ?? "") ?? false -// let timestamp = (json["timeStamp"] as? String ?? "") -// let value = (json["value"] as? String ?? "") -// -// let state: TransactionState = { -// return .pending -// }() -// -// return Transaction( -// id: hash, -// state: state, -// blockNumber: blockNumber, -// from: from, -// to: to, -// value: "0x0", //TODO -// gas: gas, -// gasPrice: gasPrice, -// confirmations: Int64(confirmations) ?? 0, -// nonce: "0x0", // TODO -// date: NSDate(timeIntervalSince1970: TimeInterval(timestamp) ?? 0) as Date -// ) -// } + func didSelectAccount(account: Account, in coordinator: AccountsCoordinator) { + delegate?.didChangeAccount(to: account, in: self) + } + + func didDeleteAccount(account: Account, in coordinator: AccountsCoordinator) { + guard !coordinator.accountsViewController.hasAccounts else { return } + coordinator.navigationController.dismiss(animated: true, completion: nil) + clean() + reset() + } } diff --git a/Trust/Transactions/Coordinators/TransactionDataCoordinator.swift b/Trust/Transactions/Coordinators/TransactionDataCoordinator.swift new file mode 100644 index 000000000..b4306299b --- /dev/null +++ b/Trust/Transactions/Coordinators/TransactionDataCoordinator.swift @@ -0,0 +1,155 @@ +// Copyright SIX DAY LLC. All rights reserved. + +import Foundation +import UIKit +import JSONRPCKit +import APIKit +import RealmSwift +import Result + +enum TransactionError: Error { + case failedToFetch +} + +protocol TransactionDataCoordinatorDelegate: class { + func didUpdate(result: Result<[Transaction], TransactionError>) +} + +class TransactionDataCoordinator { + + let storage = TransactionsStorage() + let account: Account + var viewModel: TransactionsViewModel { + return .init(transactions: self.storage.objects) + } + var timer: Timer? + weak var delegate: TransactionDataCoordinatorDelegate? + + init(account: Account) { + self.account = account + } + + func start() { + fetchTransactions() + fetchPendingTransactions() + timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(fetchPending), userInfo: nil, repeats: true) + } + + func fetch() { + fetchTransactions() + } + + func fetchTransactions() { + let startBlock: String = { + guard let transction = storage.objects.first else { return "0" } + return String(Int(transction.blockNumber) ?? 0 - 20) + }() + + let request = FetchTransactionsRequest(address: account.address.address, startBlock: startBlock) + Session.send(request) { result in + switch result { + case .success(let response): + let transactions: [Transaction] = response.map { .from(owner: self.account.address, transaction: $0) } + self.update(owner: self.account.address, items: transactions) + case .failure(let error): + self.handleError(error: error) + } + } + } + + func fetchPendingTransactions() { + Session.send(EtherServiceRequest(batch: BatchFactory().create(GetBlockByNumberRequest(block: "pending")))) { result in + switch result { + case .success(let block): + for item in block.transactions { + if item.to == self.account.address.address || item.from == self.account.address.address { + self.update(owner: self.account.address, items: [item]) + } + } + case .failure(let error): + self.handleError(error: error) + } + } + } + + @objc func fetchPending() { + fetchPendingTransactions() + } + + @objc func fetchLatest() { + fetchTransactions() + } + + func update(owner: Address, items: [ParsedTransaction]) { + let transactionItems: [Transaction] = items.map { .from(owner: owner, transaction: $0) } + update(owner: owner, items: transactionItems) + } + + func update(owner: Address, items: [Transaction]) { + storage.add(items) + handleUpdateItems() + } + + func handleError(error: Error) { + delegate?.didUpdate(result: .failure(TransactionError.failedToFetch)) + } + + func handleUpdateItems() { + delegate?.didUpdate(result: .success(self.storage.objects)) + } + + func stop() { + timer?.invalidate() + timer = nil + } +} + +extension Transaction { + static func from(owner: Address, transaction: ParsedTransaction) -> Transaction { + let state: TransactionState = { + return .pending + }() + + return Transaction( + id: transaction.hash, + owner: owner.address, + state: state, + blockNumber: transaction.blockNumber, + transactionIndex: transaction.transactionIndex, + from: transaction.from, + to: transaction.to, + value: transaction.value, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + gasUsed: transaction.gasUsed, + confirmations: Int64(transaction.confirmations) ?? 0, + nonce: transaction.nonce, + date: NSDate(timeIntervalSince1970: TimeInterval(transaction.timestamp) ?? 0) as Date + ) + } +} + +extension Transaction { + static func from(owner: Address, transaction: EtherScanTransaction) -> Transaction { + let state: TransactionState = { + return .pending + }() + + return Transaction( + id: transaction.hash, + owner: owner.address, + state: state, + blockNumber: transaction.blockNumber, + transactionIndex: transaction.transactionIndex, + from: transaction.from, + to: transaction.to, + value: transaction.value, + gas: transaction.gas, + gasPrice: transaction.gasPrice, + gasUsed: transaction.gasUsed, + confirmations: Int64(transaction.confirmations) ?? 0, + nonce: transaction.nonce, + date: NSDate(timeIntervalSince1970: TimeInterval(transaction.timestamp) ?? 0) as Date + ) + } +} diff --git a/Trust/Transactions/Storage/Transaction.swift b/Trust/Transactions/Storage/Transaction.swift index bda404bcf..5f553bd5b 100644 --- a/Trust/Transactions/Storage/Transaction.swift +++ b/Trust/Transactions/Storage/Transaction.swift @@ -8,6 +8,7 @@ class Transaction: Object { @objc dynamic var owner: String = "" @objc dynamic var state: Int = TransactionState.pending.rawValue @objc dynamic var blockNumber = "" + @objc dynamic var transactionIndex = "" @objc dynamic var from = "" @objc dynamic var to = "" @objc dynamic var value = "" @@ -23,6 +24,7 @@ class Transaction: Object { owner: String, state: TransactionState, blockNumber: String, + transactionIndex: String, from: String, to: String, value: String, @@ -38,6 +40,7 @@ class Transaction: Object { self.owner = owner self.state = state.rawValue self.blockNumber = blockNumber + self.transactionIndex = transactionIndex self.from = from self.to = to self.value = value diff --git a/Trust/Transactions/ViewControllers/TransactionViewController.swift b/Trust/Transactions/ViewControllers/TransactionViewController.swift index cbe95b69a..17f5725ac 100644 --- a/Trust/Transactions/ViewControllers/TransactionViewController.swift +++ b/Trust/Transactions/ViewControllers/TransactionViewController.swift @@ -2,6 +2,7 @@ import UIKit import StackViewController +import Result class TransactionViewController: UIViewController { @@ -11,7 +12,6 @@ class TransactionViewController: UIViewController { let stackViewController = StackViewController() let transaction: Transaction - let refreshControl = UIRefreshControl() init(transaction: Transaction) { self.transaction = transaction @@ -52,9 +52,6 @@ class TransactionViewController: UIViewController { for item in items { stackViewController.addItem(item) } - - refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged) - stackViewController.scrollView.addSubview(refreshControl) } override func viewDidLoad() { @@ -69,15 +66,6 @@ class TransactionViewController: UIViewController { stackViewController.didMove(toParentViewController: self) } - func pullToRefresh() { - fetch() - } - - func fetch() { - refreshControl.beginRefreshing() - refreshControl.endRefreshing() - } - private func item(title: String, subTitle: String) -> UIView { let titleLabel = UILabel(frame: .zero) titleLabel.translatesAutoresizingMaskIntoConstraints = false diff --git a/Trust/Transactions/ViewControllers/TransactionsViewController.swift b/Trust/Transactions/ViewControllers/TransactionsViewController.swift index c8a0bb591..deb42dee2 100644 --- a/Trust/Transactions/ViewControllers/TransactionsViewController.swift +++ b/Trust/Transactions/ViewControllers/TransactionsViewController.swift @@ -30,15 +30,11 @@ class TransactionsViewController: UIViewController { }() weak var delegate: TransactionsViewControllerDelegate? + let dataCoordinator: TransactionDataCoordinator - lazy var transactionCoordinator: TransactionCoordinator = { - let coordinator = TransactionCoordinator(account: self.account) - coordinator.delegate = self - return coordinator - }() - - init(account: Account) { + init(account: Account, dataCoordinator: TransactionDataCoordinator) { self.account = account + self.dataCoordinator = dataCoordinator tableView = UITableView(frame: .zero, style: .plain) sendButton = Button(size: .extraLarge, style: .squared) requestButton = Button(size: .extraLarge, style: .squared) @@ -93,6 +89,9 @@ class TransactionsViewController: UIViewController { stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) + dataCoordinator.delegate = self + dataCoordinator.start() + refreshControl.addTarget(self, action: #selector(pullToRefresh), for: .valueChanged) tableView.addSubview(refreshControl) @@ -116,8 +115,6 @@ class TransactionsViewController: UIViewController { super.viewWillAppear(animated) fetch() - - transactionCoordinator.start() } override func viewDidAppear(_ animated: Bool) { @@ -139,11 +136,12 @@ class TransactionsViewController: UIViewController { } func fetch() { - transactionCoordinator.fetch() + startLoading() + fetchBalance() + dataCoordinator.fetch() } func fetchBalance() { - let request = EtherServiceRequest(batch: BatchFactory().create(BalanceRequest(address: account.address.address))) Session.send(request) { [weak self] result in switch result { @@ -189,7 +187,7 @@ extension TransactionsViewController: UITableViewDelegate { } } -extension TransactionsViewController: TransactionCoordinatorDelegate { +extension TransactionsViewController: TransactionDataCoordinatorDelegate { func didUpdate(result: Result<[Transaction], TransactionError>) { switch result { case .success(let items): diff --git a/Trust/Transactions/ViewModels/TransactionViewCellViewModel.swift b/Trust/Transactions/ViewModels/TransactionViewCellViewModel.swift index 26a64b200..6354dab86 100644 --- a/Trust/Transactions/ViewModels/TransactionViewCellViewModel.swift +++ b/Trust/Transactions/ViewModels/TransactionViewCellViewModel.swift @@ -57,7 +57,7 @@ struct TransactionViewCellViewModel { case .error, .completed: return .white case .pending: - return Colors.lightGray + return UIColor(hex: "f4f4f4") } } } diff --git a/Trust/Transactions/ViewModels/TransactionsViewModel.swift b/Trust/Transactions/ViewModels/TransactionsViewModel.swift index c313f6cde..b7b601f45 100644 --- a/Trust/Transactions/ViewModels/TransactionsViewModel.swift +++ b/Trust/Transactions/ViewModels/TransactionsViewModel.swift @@ -24,9 +24,11 @@ struct TransactionsViewModel { currentItems.insert(transaction, at: 0) newItems[date] = currentItems } - + //TODO. IMPROVE perfomance let tuple = newItems.map { (key, values) in return (date: key, transactions: values) } - items = tuple.sorted { (object1, object2) -> Bool in return object1.date > object2.date } + items = tuple.sorted { (object1, object2) -> Bool in + return TransactionsViewModel.formatter.date(from: object1.date)! > TransactionsViewModel.formatter.date(from: object2.date)! + } } var title: String { diff --git a/Trust/UI/Coordinator.swift b/Trust/UI/Coordinator.swift new file mode 100644 index 000000000..e50581d06 --- /dev/null +++ b/Trust/UI/Coordinator.swift @@ -0,0 +1,17 @@ +// Copyright SIX DAY LLC. All rights reserved. + +import Foundation + +protocol Coordinator: class { + var coordinators: [Coordinator] {get set} +} + +extension Coordinator { + func addCoordinator(_ coordinator: Coordinator) { + coordinators.append(coordinator) + } + + func removeCoordinator(_ coordinator: Coordinator) { + coordinators = coordinators.filter { $0 !== coordinator } + } +} diff --git a/TrustTests/Coordinators/AppCoordinatorTests.swift b/TrustTests/Coordinators/AppCoordinatorTests.swift index 6ea0a7fbf..aeb77c8dc 100644 --- a/TrustTests/Coordinators/AppCoordinatorTests.swift +++ b/TrustTests/Coordinators/AppCoordinatorTests.swift @@ -42,37 +42,7 @@ class AppCoordinatorTests: XCTestCase { XCTAssertTrue(coordinator.rootNavigationController.viewControllers[0] is WelcomeViewController) } - - func testShowAccounts() { - let coordinator = AppCoordinator( - window: UIWindow(), - keystore: FakeKeystore( - accounts: [.make()] - ), - rootNavigationController: FakeNavigationController() - ) - coordinator.start() - - coordinator.showAccounts() - - XCTAssertTrue((coordinator.rootNavigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is AccountsViewController) - } - - func testShowSettings() { - let coordinator = AppCoordinator( - window: UIWindow(), - keystore: FakeKeystore( - accounts: [.make()] - ), - rootNavigationController: FakeNavigationController() - ) - coordinator.start() - - coordinator.showSettings() - - XCTAssertTrue((coordinator.rootNavigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is SettingsViewController) - } - + func testStartWalletCoordinator() { let coordinator = AppCoordinator( window: UIWindow(), @@ -102,19 +72,4 @@ class AppCoordinatorTests: XCTestCase { XCTAssertTrue(coordinator.rootNavigationController.viewControllers[0] is TransactionsViewController) } - - func testShowTokens() { - let coordinator = AppCoordinator( - window: UIWindow(), - keystore: FakeKeystore( - accounts: [.make()] - ), - rootNavigationController: FakeNavigationController() - ) - coordinator.start() - - coordinator.showTokens(for: .make()) - - XCTAssertTrue(coordinator.rootNavigationController.viewControllers[1] is TokensViewController) - } } diff --git a/TrustTests/Coordinators/TransactionCoordinatorTests.swift b/TrustTests/Coordinators/TransactionCoordinatorTests.swift new file mode 100644 index 000000000..b5be0e6e6 --- /dev/null +++ b/TrustTests/Coordinators/TransactionCoordinatorTests.swift @@ -0,0 +1,40 @@ +// Copyright SIX DAY LLC. All rights reserved. + +import XCTest +@testable import Trust + +class TransactionCoordinatorTests: XCTestCase { + + func testShowTokens() { + let coordinator = TransactionCoordinator( + account: .make(), + rootNavigationController: FakeNavigationController() + ) + + coordinator.showTokens(for: .make()) + + XCTAssertTrue(coordinator.navigationController.viewControllers[0] is TokensViewController) + } + + func testShowAccounts() { + let coordinator = TransactionCoordinator( + account: .make(), + rootNavigationController: FakeNavigationController() + ) + + coordinator.showAccounts() + + XCTAssertTrue((coordinator.navigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is AccountsViewController) + } + + func testShowSettings() { + let coordinator = TransactionCoordinator( + account: .make(), + rootNavigationController: FakeNavigationController() + ) + + coordinator.showSettings() + + XCTAssertTrue((coordinator.navigationController.presentedViewController as? UINavigationController)?.viewControllers[0] is SettingsViewController) + } +} diff --git a/TrustTests/EtherClient/EtherKeystoreTests.swift b/TrustTests/EtherClient/EtherKeystoreTests.swift index 5e762890a..85cf05f15 100644 --- a/TrustTests/EtherClient/EtherKeystoreTests.swift +++ b/TrustTests/EtherClient/EtherKeystoreTests.swift @@ -49,7 +49,7 @@ class EtherKeystoreTests: XCTestCase { return XCTFail() } - XCTAssertEqual("0x5E9c27156a612a2D516C74c7a80af107856F8539", account.address.address) + XCTAssertEqual("0x5e9c27156a612a2d516c74c7a80af107856f8539", account.address.address) XCTAssertEqual(1, keystore.accounts.count) } diff --git a/TrustTests/Factories/FakeNavigationController.swift b/TrustTests/Factories/FakeNavigationController.swift index cdbcb88b3..630549725 100644 --- a/TrustTests/Factories/FakeNavigationController.swift +++ b/TrustTests/Factories/FakeNavigationController.swift @@ -4,12 +4,18 @@ import Foundation import UIKit class FakeNavigationController: UINavigationController { - + + private var _presentedViewController: UIViewController? + + override var presentedViewController: UIViewController? { + return _presentedViewController + } + override func pushViewController(_ viewController: UIViewController, animated: Bool) { super.pushViewController(viewController, animated: false) } override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { - super.present(viewControllerToPresent, animated: false, completion: completion) + _presentedViewController = viewControllerToPresent } } diff --git a/TrustTests/Factories/Transaction.swift b/TrustTests/Factories/Transaction.swift index 85aef73d0..621088659 100644 --- a/TrustTests/Factories/Transaction.swift +++ b/TrustTests/Factories/Transaction.swift @@ -9,6 +9,7 @@ extension Transaction { owner: String = "0x1", state: TransactionState = .pending, blockNumber: String = "0x1", + transactionIndex: String = "0x1", from: String = "0x1", to: String = "0x1", value: String = "1", @@ -24,6 +25,7 @@ extension Transaction { owner: owner, state: state, blockNumber: blockNumber, + transactionIndex: transactionIndex, from: from, to: to, value: value,