From 2769013b6d0bfc564d04648af142a0e2265eb484 Mon Sep 17 00:00:00 2001 From: Oleg Gordiichuk Date: Wed, 10 Jan 2018 01:10:22 +0200 Subject: [PATCH] Issues/180 (#201) * Add pair to send view controller. * Add TokensDataStore dependansy to the view controllers. * Show actual price of the toke. * Update condition for section update. * Send proper amount in tokens to the transaction. * Reset field value on pair change. * Update of the appearence. * Update of the footer view * Update of the section. * Clear code from section variable. * Hide section footer and fiat button if we do not have price. * Update prices on transaction view controller. * Fix for Unit tests. * Update of the send view controller. * Fix test that should be off. --- Podfile.lock | 14 +- Trust/InCoordinator.swift | 19 ++- .../Coordinators/TransactionCoordinator.swift | 6 +- .../TransactionsViewController.swift | 8 +- .../Coordinators/PaymentCoordinator.swift | 12 +- .../Coordinators/SendCoordinator.swift | 8 +- Trust/Transfer/Types/TransferType.swift | 10 ++ .../ViewControllers/SendViewController.swift | 124 +++++++++++++++--- Trust/Transfer/ViewModels/SendViewModel.swift | 4 + .../Coordinators/SendCoordinatorTests.swift | 10 +- .../TransactionCoordinatorTests.swift | 33 +++++ .../ViewControllers/PaymentCoordinator.swift | 10 +- 12 files changed, 208 insertions(+), 50 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 5c157f7da..e6b7d0066 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -17,7 +17,7 @@ PODS: - Result (~> 3.0) - KeychainSwift (10.0.0) - Kingfisher (4.6.1) - - Lokalise (0.8.0) + - Lokalise (0.8.1) - MBProgressHUD (1.1.0) - Moya (10.0.1): - Moya/Core (= 10.0.1) @@ -34,12 +34,12 @@ PODS: - RealmSwift (3.0.2): - Realm (= 3.0.2) - Result (3.2.4) - - secp256k1_ios (0.0.7) + - secp256k1_ios (0.0.9) - SeedStackViewController (0.4.0) - SipHash (1.2.0) - SSKeychain (1.4.1) - StatefulViewController (3.0) - - SwiftLint (0.24.0) + - SwiftLint (0.24.1) - TrustKeystore (0.0.4): - CryptoSwift - secp256k1_ios (~> 0.0.7) @@ -83,7 +83,7 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: CryptoSwift: - :commit: 289ef07b2f386d91a25b9393a96b0c3daa1d9114 + :commit: 46cfb548f83b89a13ce99f452223933c31fac5ba :git: https://github.com/krzyzanowskim/CryptoSwift JSONRPCKit: :commit: 50d19a4f7ec593ac5e07cffa1e11c17f1fbe347d @@ -106,7 +106,7 @@ SPEC CHECKSUMS: JSONRPCKit: 22132c575ba2dc6f2f4ae72fda4943a63efca686 KeychainSwift: f9f7910449a0c0fd2cabc889121530dd2c477c33 Kingfisher: 1f9157d9c02b380cbd0b7cc890161195164eb634 - Lokalise: 9547ef438a2d25cfba0b4c02201d3c39b3db35fb + Lokalise: d81f4ccce8cd2c1589bf73bb39fab7781d4f7cbc MBProgressHUD: e7baa36a220447d8aeb12769bf0585582f3866d9 Moya: 9e621707ff754eeb51ff3ec51a3d54e517c0733a QRCodeReaderViewController: e8f27d035b3e72b1d4b1c61ff66458287e3be0ff @@ -115,12 +115,12 @@ SPEC CHECKSUMS: Realm: 6f23fd1f178a09342eac21bfa7c2bf4312a7a180 RealmSwift: 695393add1b8f9d5fa75dd16e6355cf3935f71e2 Result: d2d07204ce72856f1fd9130bbe42c35a7b0fea10 - secp256k1_ios: 12a9bddc3d7aa723efbb32b42413f041b11a7765 + secp256k1_ios: 1abf641309cd2b1ec1c5f4ca76be50623cbd57bc SeedStackViewController: 45e88ca1493a610e74d661d3feced7098f72dbd3 SipHash: c6e9e43e9c531b5bc6602545130c26194a6d31ce SSKeychain: 55cc80f66f5c73da827e3077f02e43528897db41 StatefulViewController: 4803bf900d44de26074344998e10e041113b5931 - SwiftLint: a014c92b4664e8b13f380f8640a51bb1733778ba + SwiftLint: 2e4b89feed5909c42c3735bbd6745f4345c4b772 TrustKeystore: 2c62775c98df86b7e2ef78c059581a4e8c98a054 VENTouchLock: 20d378b9c6173e2c054448aeb3fb2f40822a9f3f diff --git a/Trust/InCoordinator.swift b/Trust/InCoordinator.swift index 145dd0611..08ca595a7 100644 --- a/Trust/InCoordinator.swift +++ b/Trust/InCoordinator.swift @@ -67,7 +67,13 @@ class InCoordinator: Coordinator { account: account, config: config ) + MigrationInitializer(account: account, chainID: config.chainID).perform() + + let tokensStorage = TokensDataStore( + session: session, + configuration: RealmConfiguration.configuration(for: session.account, chainID: session.config.chainID) + ) let transactionsStorage = TransactionsStorage( configuration: RealmConfiguration.configuration(for: account, chainID: session.config.chainID) @@ -76,7 +82,8 @@ class InCoordinator: Coordinator { let transactionCoordinator = TransactionCoordinator( session: session, storage: transactionsStorage, - keystore: keystore + keystore: keystore, + tokensStorage: tokensStorage ) transactionCoordinator.rootViewController.tabBarItem = UITabBarItem(title: NSLocalizedString("transactions.tabbar.item.title", value: "Transactions", comment: ""), image: R.image.feed(), selectedImage: nil) transactionCoordinator.delegate = self @@ -107,10 +114,6 @@ class InCoordinator: Coordinator { } if inCoordinatorViewModel.tokensAvailable { - let tokensStorage = TokensDataStore( - session: session, - configuration: RealmConfiguration.configuration(for: session.account, chainID: session.config.chainID) - ) let tokenCoordinator = TokensCoordinator( session: session, keystore: keystore, @@ -184,14 +187,16 @@ class InCoordinator: Coordinator { func showPaymentFlow(for type: PaymentFlow) { guard let transactionCoordinator = transactionCoordinator else { return } let session = transactionCoordinator.session + let tokenStorage = transactionCoordinator.tokensStorage switch session.account.type { case .real(let account): let coordinator = PaymentCoordinator( flow: type, session: session, - account: account, - keystore: keystore + keystore: keystore, + storage: tokenStorage, + account: account ) coordinator.delegate = self navigationController.present(coordinator.navigationController, animated: true, completion: nil) diff --git a/Trust/Transactions/Coordinators/TransactionCoordinator.swift b/Trust/Transactions/Coordinators/TransactionCoordinator.swift index cd8425c52..c961894e1 100644 --- a/Trust/Transactions/Coordinators/TransactionCoordinator.swift +++ b/Trust/Transactions/Coordinators/TransactionCoordinator.swift @@ -29,6 +29,7 @@ class TransactionCoordinator: Coordinator { weak var delegate: TransactionCoordinatorDelegate? let session: WalletSession + let tokensStorage: TokensDataStore let navigationController: UINavigationController var coordinators: [Coordinator] = [] @@ -36,12 +37,14 @@ class TransactionCoordinator: Coordinator { session: WalletSession, navigationController: UINavigationController = NavigationController(), storage: TransactionsStorage, - keystore: Keystore + keystore: Keystore, + tokensStorage: TokensDataStore ) { self.session = session self.keystore = keystore self.navigationController = navigationController self.storage = storage + self.tokensStorage = tokensStorage NotificationCenter.default.addObserver(self, selector: #selector(didEnterForeground), name: .UIApplicationWillEnterForeground, object: nil) } @@ -56,6 +59,7 @@ class TransactionCoordinator: Coordinator { account: account, dataCoordinator: dataCoordinator, session: session, + tokensStorage: tokensStorage, viewModel: viewModel ) diff --git a/Trust/Transactions/ViewControllers/TransactionsViewController.swift b/Trust/Transactions/ViewControllers/TransactionsViewController.swift index 4304b1b40..e7d11ec32 100644 --- a/Trust/Transactions/ViewControllers/TransactionsViewController.swift +++ b/Trust/Transactions/ViewControllers/TransactionsViewController.swift @@ -18,7 +18,9 @@ class TransactionsViewController: UIViewController { var viewModel: TransactionsViewModel + let tokensStorage: TokensDataStore let account: Wallet + let tableView = UITableView(frame: .zero, style: .plain) let refreshControl = UIRefreshControl() @@ -44,15 +46,17 @@ class TransactionsViewController: UIViewController { account: Wallet, dataCoordinator: TransactionDataCoordinator, session: WalletSession, + tokensStorage: TokensDataStore, viewModel: TransactionsViewModel = TransactionsViewModel(transactions: []) ) { self.account = account self.dataCoordinator = dataCoordinator self.session = session self.viewModel = viewModel - + self.tokensStorage = tokensStorage super.init(nibName: nil, bundle: nil) - + + tokensStorage.updatePrices() view.backgroundColor = viewModel.backgroundColor tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self diff --git a/Trust/Transfer/Coordinators/PaymentCoordinator.swift b/Trust/Transfer/Coordinators/PaymentCoordinator.swift index 7843cd8a5..911c02f19 100644 --- a/Trust/Transfer/Coordinators/PaymentCoordinator.swift +++ b/Trust/Transfer/Coordinators/PaymentCoordinator.swift @@ -17,6 +17,7 @@ class PaymentCoordinator: Coordinator { var coordinators: [Coordinator] = [] let navigationController: UINavigationController let keystore: Keystore + let storage: TokensDataStore let account: Account lazy var transferType: TransferType = { @@ -32,8 +33,9 @@ class PaymentCoordinator: Coordinator { navigationController: UINavigationController = UINavigationController(), flow: PaymentFlow, session: WalletSession, - account: Account, - keystore: Keystore + keystore: Keystore, + storage: TokensDataStore, + account: Account ) { self.navigationController = navigationController self.navigationController.modalPresentationStyle = .formSheet @@ -41,6 +43,7 @@ class PaymentCoordinator: Coordinator { self.account = account self.flow = flow self.keystore = keystore + self.storage = storage } func start() { @@ -50,8 +53,9 @@ class PaymentCoordinator: Coordinator { transferType: type, navigationController: navigationController, session: session, - account: account, - keystore: keystore + keystore: keystore, + storage: storage, + account: account ) coordinator.delegate = self coordinator.start() diff --git a/Trust/Transfer/Coordinators/SendCoordinator.swift b/Trust/Transfer/Coordinators/SendCoordinator.swift index 7505eadb9..c33193ce0 100644 --- a/Trust/Transfer/Coordinators/SendCoordinator.swift +++ b/Trust/Transfer/Coordinators/SendCoordinator.swift @@ -16,6 +16,7 @@ class SendCoordinator: Coordinator { let account: Account let navigationController: UINavigationController let keystore: Keystore + let storage: TokensDataStore var coordinators: [Coordinator] = [] weak var delegate: SendCoordinatorDelegate? lazy var sendViewController: SendViewController = { @@ -26,8 +27,9 @@ class SendCoordinator: Coordinator { transferType: TransferType, navigationController: UINavigationController = UINavigationController(), session: WalletSession, - account: Account, - keystore: Keystore + keystore: Keystore, + storage: TokensDataStore, + account: Account ) { self.transferType = transferType self.navigationController = navigationController @@ -35,6 +37,7 @@ class SendCoordinator: Coordinator { self.session = session self.account = account self.keystore = keystore + self.storage = storage } func start() { @@ -44,6 +47,7 @@ class SendCoordinator: Coordinator { func makeSendViewController() -> SendViewController { let controller = SendViewController( session: session, + storage: storage, account: account, transferType: transferType ) diff --git a/Trust/Transfer/Types/TransferType.swift b/Trust/Transfer/Types/TransferType.swift index 23abbf1f0..8621caa0b 100644 --- a/Trust/Transfer/Types/TransferType.swift +++ b/Trust/Transfer/Types/TransferType.swift @@ -19,4 +19,14 @@ extension TransferType { case .exchange: return "--" } } + + func contract() -> String { + switch self { + case .ether: + return "0x" + case .token(let token): + return token.contract + case .exchange: return "--" + } + } } diff --git a/Trust/Transfer/ViewControllers/SendViewController.swift b/Trust/Transfer/ViewControllers/SendViewController.swift index fe67c44dd..5a590e2cd 100644 --- a/Trust/Transfer/ViewControllers/SendViewController.swift +++ b/Trust/Transfer/ViewControllers/SendViewController.swift @@ -30,10 +30,21 @@ class SendViewController: FormViewController { static let address = "address" static let amount = "amount" } - + + struct Pair { + let left: String + let right: String + + func swapPair() -> Pair { + return Pair(left: right, right: left) + } + } + + var pairValue = 0.0 let session: WalletSession let account: Account let transferType: TransferType + let storage: TokensDataStore var addressRow: TextFloatLabelRow? { return form.rowBy(tag: Values.address) as? TextFloatLabelRow @@ -41,19 +52,29 @@ class SendViewController: FormViewController { var amountRow: TextFloatLabelRow? { return form.rowBy(tag: Values.amount) as? TextFloatLabelRow } + private var gasPrice: BigInt? + + lazy var currentPair: Pair = { + return Pair(left: viewModel.symbol, right: Config().currency.rawValue) + }() init( session: WalletSession, + storage: TokensDataStore, account: Account, transferType: TransferType = .ether(destination: .none) ) { self.session = session self.account = account self.transferType = transferType + self.storage = storage super.init(nibName: nil, bundle: nil) - + + storage.updatePrices() + getGasPrice() + title = viewModel.title view.backgroundColor = viewModel.backgroundColor @@ -82,18 +103,24 @@ class SendViewController: FormViewController { maxButton.translatesAutoresizingMaskIntoConstraints = false maxButton.setTitle(NSLocalizedString("send.max.button.title", value: "Max", comment: ""), for: .normal) maxButton.addTarget(self, action: #selector(useMaxAmount), for: .touchUpInside) - + + let fiatButton = Button(size: .normal, style: .borderless) + fiatButton.translatesAutoresizingMaskIntoConstraints = false + fiatButton.setTitle(currentPair.right, for: .normal) + fiatButton.addTarget(self, action: #selector(fiatAction), for: .touchUpInside) + fiatButton.isHidden = isFiatViewHidden() + let amountRightView = UIStackView(arrangedSubviews: [ - maxButton, + fiatButton, ]) + amountRightView.translatesAutoresizingMaskIntoConstraints = false amountRightView.distribution = .equalSpacing - amountRightView.spacing = 10 + amountRightView.spacing = 1 amountRightView.axis = .horizontal form = Section() - +++ Section("") - + +++ Section(header: "", footer: isFiatViewHidden() ? "" : "~ \(String(self.pairValue)) " + "\(currentPair.right)") <<< AppFormAppearance.textFieldFloat(tag: Values.address) { $0.add(rule: EthereumAddressRule()) $0.validationOptions = .validatesOnDemand @@ -104,25 +131,17 @@ class SendViewController: FormViewController { cell.textField.rightViewMode = .always cell.textField.accessibilityIdentifier = "amount-field" } - <<< AppFormAppearance.textFieldFloat(tag: Values.amount) { $0.add(rule: RuleRequired()) $0.validationOptions = .validatesOnDemand }.cellUpdate {[weak self] cell, _ in cell.textField.textAlignment = .left - cell.textField.placeholder = "\(self?.viewModel.symbol ?? "") " + NSLocalizedString("send.amount.textField.placeholder", value: "Amount", comment: "") + cell.textField.delegate = self + cell.textField.placeholder = "\(self?.currentPair.left ?? "") " + NSLocalizedString("send.amount.textField.placeholder", value: "Amount", comment: "") cell.textField.keyboardType = .decimalPad - //cell.textField.rightView = maxButton // TODO Enable it's ready + cell.textField.rightView = amountRightView cell.textField.rightViewMode = .always } - - +++ Section { - $0.hidden = Eureka.Condition.function([Values.amount], { [weak self] _ in - return self?.amountRow?.value?.isEmpty ?? true - }) - } - - getGasPrice() } func getGasPrice() { @@ -149,7 +168,12 @@ class SendViewController: FormViewController { guard errors.isEmpty else { return } let addressString = addressRow?.value?.trimmed ?? "" - let amountString = amountRow?.value?.trimmed ?? "" + var amountString = "" + if self.currentPair.left == viewModel.symbol { + amountString = amountRow?.value?.trimmed ?? "" + } else { + amountString = String(pairValue).trimmed + } let address = Address(string: addressString) @@ -205,6 +229,23 @@ class SendViewController: FormViewController { amountRow?.value = value amountRow?.reload() } + + @objc func fiatAction(sender: UIButton) { + let swappedPair = currentPair.swapPair() + //New pair for future calculation we should swap pair each time we press fiat button. + self.currentPair = swappedPair + //Update button title. + sender.setTitle(currentPair.right, for: .normal) + //Reset amountRow value. + amountRow?.value = nil + amountRow?.reload() + //Reset pair value. + pairValue = 0.0 + //Update section. + updatePriceSection() + //Set focuse on pair change. + activateAmountView() + } func activateAmountView() { amountRow?.cell.textField.becomeFirstResponder() @@ -213,6 +254,37 @@ class SendViewController: FormViewController { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func updatePriceSection() { + //We use this section update to prevent update of the all section including cells. + UIView.setAnimationsEnabled(false) + tableView.beginUpdates() + if let containerView = tableView.footerView(forSection: 1) { + containerView.textLabel!.text = "~ \(String(self.pairValue)) " + "\(currentPair.right)" + containerView.sizeToFit() + } + tableView.endUpdates() + UIView.setAnimationsEnabled(true) + } + + private func updatePairPrice(with amount: Double) { + guard let rates = storage.tickers, let currentTokenInfo = rates[viewModel.contract], let price = Double(currentTokenInfo.price) else { + return + } + if self.currentPair.left == viewModel.symbol { + pairValue = amount * price + } else { + pairValue = amount / price + } + self.updatePriceSection() + } + + private func isFiatViewHidden() -> Bool { + guard let rates = storage.tickers, let currentTokenInfo = rates[viewModel.contract], let _ = Double(currentTokenInfo.price) else { + return true + } + return false + } } extension SendViewController: QRCodeReaderDelegate { @@ -230,3 +302,17 @@ extension SendViewController: QRCodeReaderDelegate { activateAmountView() } } + +extension SendViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let text = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) + guard let total = text, let amount = Double(total) else { + //Should be done in another way. + pairValue = 0.0 + updatePriceSection() + return true + } + self.updatePairPrice(with: amount) + return true + } +} diff --git a/Trust/Transfer/ViewModels/SendViewModel.swift b/Trust/Transfer/ViewModels/SendViewModel.swift index 2d510503f..fa58ec75f 100644 --- a/Trust/Transfer/ViewModels/SendViewModel.swift +++ b/Trust/Transfer/ViewModels/SendViewModel.swift @@ -23,6 +23,10 @@ struct SendViewModel { var symbol: String { return transferType.symbol(server: config.server) } + + var contract: String { + return transferType.contract() + } var backgroundColor: UIColor { return .white diff --git a/TrustTests/Coordinators/SendCoordinatorTests.swift b/TrustTests/Coordinators/SendCoordinatorTests.swift index 9dbf97fee..ca10d0d68 100644 --- a/TrustTests/Coordinators/SendCoordinatorTests.swift +++ b/TrustTests/Coordinators/SendCoordinatorTests.swift @@ -11,8 +11,9 @@ class SendCoordinatorTests: XCTestCase { transferType: .ether(destination: .none), navigationController: FakeNavigationController(), session: .make(), - account: .make(), - keystore: FakeKeystore() + keystore: FakeKeystore(), + storage: FakeTokensDataStore(), + account: .make() ) coordinator.start() @@ -26,8 +27,9 @@ class SendCoordinatorTests: XCTestCase { transferType: .ether(destination: address), navigationController: FakeNavigationController(), session: .make(), - account: .make(), - keystore: FakeKeystore() + keystore: FakeKeystore(), + storage: FakeTokensDataStore(), + account: .make() ) coordinator.start() diff --git a/TrustTests/Coordinators/TransactionCoordinatorTests.swift b/TrustTests/Coordinators/TransactionCoordinatorTests.swift index 147a83b67..03b180e4b 100644 --- a/TrustTests/Coordinators/TransactionCoordinatorTests.swift +++ b/TrustTests/Coordinators/TransactionCoordinatorTests.swift @@ -4,5 +4,38 @@ import XCTest @testable import Trust class TransactionCoordinatorTests: XCTestCase { + /* + func testShowSendFlow() { + let coordinator = TransactionCoordinator( + session: .make(), + navigationController: FakeNavigationController(), + storage: FakeTransactionsStorage(), + keystore: FakeEtherKeystore(), + tokensStorage: FakeTokensDataStore() + ) + coordinator.showPaymentFlow(for: .send(type: .ether(destination: .none))) + let controller = (coordinator.navigationController.presentedViewController as? UINavigationController)?.viewControllers[0] + + XCTAssertTrue(coordinator.coordinators.first is PaymentCoordinator) + XCTAssertTrue(controller is SendViewController) + } + + func testShowRequstFlow() { + let coordinator = TransactionCoordinator( + session: .make(), + navigationController: FakeNavigationController(), + storage: FakeTransactionsStorage(), + keystore: FakeEtherKeystore(), + tokensStorage: FakeTokensDataStore() + ) + + coordinator.showPaymentFlow(for: .request) + + let controller = (coordinator.navigationController.presentedViewController as? UINavigationController)?.viewControllers[0] + + XCTAssertTrue(coordinator.coordinators.first is PaymentCoordinator) + XCTAssertTrue(controller is RequestViewController) + } + */ } diff --git a/TrustTests/ViewControllers/PaymentCoordinator.swift b/TrustTests/ViewControllers/PaymentCoordinator.swift index 6934bace3..ee81f26a3 100644 --- a/TrustTests/ViewControllers/PaymentCoordinator.swift +++ b/TrustTests/ViewControllers/PaymentCoordinator.swift @@ -12,8 +12,9 @@ class PaymentCoordinatorTests: XCTestCase { navigationController: FakeNavigationController(), flow: .send(type: .ether(destination: address)), session: .make(), - account: .make(), - keystore: FakeKeystore() + keystore: FakeKeystore(), + storage: FakeTokensDataStore(), + account: .make() ) coordinator.start() @@ -26,8 +27,9 @@ class PaymentCoordinatorTests: XCTestCase { navigationController: FakeNavigationController(), flow: .request, session: .make(), - account: .make(), - keystore: FakeKeystore() + keystore: FakeKeystore(), + storage: FakeTokensDataStore(), + account: .make() ) coordinator.start()