// // CCCPControllerGenerateTokens.swift // List // // Created by Saveliy Stavitsky on 7/20/20. // Copyright © 2020 Igor Danich. All rights reserved. // import UIKit private typealias Contract = Network.Model.Contract private typealias BlockchainAction = Network.Model.Blockchain.Action class CCCPControllerGenerateTokens: UIViewController { @IBOutlet private var mainStackView: UIStackView! @IBOutlet private weak var tokeGiveView: CommonViewCard! @IBOutlet private weak var amountTextField: CommonTextField! @IBOutlet private var tokenGetStackView: UIStackView! @IBOutlet private weak var tokeGetView: CommonViewCard! @IBOutlet private var labelsStackView: UIStackView! @IBOutlet private weak var youGiveInfoLbl: CommonViewInfoLabel! @IBOutlet private weak var youGetInfoLbl: CommonViewInfoLabel! @IBOutlet var youGiveTokenSelectorView: CCCPViewTokenSelector! @IBOutlet var youGetTokenSelectorView: CCCPViewTokenSelector! @IBOutlet var generateTokensButton: CommonButtonAction! private let cashRatesService = CashRatesService() private var cashRates: [String]? { didSet { stateChanged() } } var selectSymbol = "" var tokensIncome: [Network.Model.TokenSmall] = [] var tokensIncomeCoef: Decimal? { if let selectedToken = youGiveTokenSelectorView.selectedToken { return tokensIncome .first(where: { $0.quantity.symbol == selectedToken.symbol })? .quantity.amount.toDecimal() } else { return nil } } var tokens: [Network.Model.Token] = [] { didSet { youGiveTokenSelectorView.tokens = tokens.filter({ $0.isListContract && $0.amount > 0 }) if let index = youGiveTokenSelectorView.tokens.enumerated() .first(where: { $0.element.symbol == self.selectSymbol })?.offset, !selectSymbol.isEmpty { youGiveTokenSelectorView.index = index self.cashRatesService.fetchRates(listSymbol: selectSymbol, completion: { self.cashRates = $0 }) selectSymbol = "" } tokeGiveView.action = .cards( tokens: youGiveTokenSelectorView.tokens, selected: youGiveTokenSelectorView.selectedToken, empty: .menu( image: Asset.commonToken.image.tinted(with: Asset.deepWater.color), title: L10n.Cccp.IssueTokens.youGiveSelectorTitle ), title: L10n.Cccp.IssueTokens.youGiveSelectorTitle, { [weak self] in self?.youGiveTokenSelectorView.index = self?.youGiveTokenSelectorView.tokens.index(of: $0.uuid) ?? -1 if let token = self?.youGiveTokenSelectorView.selectedToken { self?.cashRatesService.fetchRates(listSymbol: token.symbol, completion: { self?.cashRates = $0 }) } }) } } var cashTokens: [Network.Model.Token] = [] { didSet { youGetTokenSelectorView.tokens = cashTokens.map({.init( symbol: $0.symbol, precision: $0.precision, amount: 0, contract: Network.servers.current.contract(.cash) )}) tokeGetView.action = .cards(tokens: youGetTokenSelectorView.tokens, selected: youGetTokenSelectorView.selectedToken, { [weak self] in self?.youGetTokenSelectorView.index = self?.youGetTokenSelectorView.tokens.index(of: $0.uuid) ?? -1 }) } } var selectedTokens: (Network.Model.Token, Network.Model.Token, String)? { guard let cashRates = cashRates, let symbol = youGetTokenSelectorView.selectedToken?.symbol else { return nil } guard let tokenGive = youGiveTokenSelectorView.selectedToken, let tokenGet = cashRates.first(where: { $0.symbol == symbol }).map({ var token = cashTokens.first(where: { $0.symbol == symbol }) token?.certCoef = $0.amount.toDecimal() return token }) ?? cashTokens.first(where: { $0.symbol == symbol }), let certificate = Wallet.Service.Tokens.shared.combineCertificate(list: tokenGive, cash: tokenGet) else { return nil } return (tokenGive, tokenGet, certificate.symbol) } lazy var titleLabel: UILabel = { let label = UILabel() label.backgroundColor = .clear label.numberOfLines = 2 label.font = Font.font(style: .medium, size: 20) label.textAlignment = .center label.textColor = Asset.textCoal.color label.text = L10n.Cccp.IssueTokens.title return label }() override func viewDidLoad() { super.viewDidLoad() // self.navigationItem.titleView = titleLabel navigationItem.title = L10n.Cccp.IssueTokens.title navigationItem.leftBarButtonItem = .pop(self) tokeGiveView.action = .cards( tokens: [], selected: nil, empty: .menu( image: Asset.commonToken.image.tinted(with: Asset.deepWater.color), title: L10n.Cccp.IssueTokens.youGiveSelectorTitle ) ) tokeGetView.action = .cards(collection: nil) amountTextField.keyboardType = .numberPad amountTextField.delegate = self youGiveTokenSelectorView.titleLabel.text = L10n.Cccp.GenerateTokens.youGiveTitle("") youGiveTokenSelectorView.secondTitleLabel.isHidden = true youGiveTokenSelectorView.notesSectionStackView.isHidden = true youGiveTokenSelectorView.notesSectionSubStackView.removeFromSuperview() youGiveTokenSelectorView.notesTitleLabel.attributedText = L10n.Cccp.DestroyTokens.noEnoughtBalance.attributed .applying(attribute: .foregroundColor, value: Asset.liSTRedEnd.color) .applying(attribute: .font, value: FontFamily.GolosUI.medium.font(size: 14)!) youGiveTokenSelectorView.balanceStackView.isHidden = false youGiveTokenSelectorView.parentViewController = self youGetTokenSelectorView.secondTitleLabel.isHidden = true youGetTokenSelectorView.countSectionStackView.isHidden = true youGetTokenSelectorView.notesTitleLabel.text = L10n.Cccp.GenerateTokens.youGetYouGetTitle youGetTokenSelectorView.parentViewController = self observers.append(contentsOf: [ Notification.subscribe(name: .didChangeAccount) { _ in Loader.show() }, Notification.subscribe(name: .didUpdateBalances) { [weak self] _ in self?.setupView() } ]) setupView() } var observers: [AnyObject] = [] deinit { observers.forEach({ Notification.unsubscribe(observer: $0) }) } func setupView() { CCCP.Service.Swaps.shared.fetch { [weak self] in self?.tokensIncome = $0 self?.cashTokens = Wallet.Service.Tokens.shared.cashTokens self?.tokens = Wallet.Service.Tokens.shared.balances.collection Loader.hide() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Accounts().isLocked = true } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) Accounts().isLocked = false } @IBAction func generateTokensPressed(_ sender: Any) { guard let user = Accounts().current, let selectedTokens = selectedTokens, youGiveTokenSelectorView.count > 0 else { return } let createActions = openBalancesBlockchainActions(user: user, selectedTokens: selectedTokens) let generateActions = generateTokensBlockchainActions(user: user, selectedTokens: selectedTokens) Popup.show(title: navigationItem.title, views: [labelsStackView], submit: L10n.Common.Button.confirm, in: self, { [unowned self] ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { Loader.show(in: ctrl) Network.Service.Blockchain.execute(actions: createActions + generateActions, privateKeys: [$0]) { result in // guard let self = self else { return } Loader.hide(in: ctrl) ctrl.dismiss(animated: true) // Alert.notify(NSError.error(result), pop: self) switch result { case .success(let transactionId): delayed(0.5) { //needed because labelsStackView will free inly after previous popup hde Popup.show(title: L10n.Cccp.IssueTokens.completeTitle, views: [labelsStackView], submit: L10n.Common.Button.checkOnBloksIO, submitStyle: .outline, cancel: L10n.Common.Button.done, in: self, { _ in guard let checkTransactionUrl = URL(transactionId: transactionId) else { return } UIApplication.shared.open(checkTransactionUrl, options: [:], completionHandler: nil) }, { if let secondViewController = navigationController?.viewControllers.dropFirst().first { navigationController?.popToViewController(secondViewController, animated: true) } }) } case let .failure(error): Alert.notify(error, pop: self) } } } }) { [weak self] in guard let self = self else { return } self.mainStackView.addArrangedSubview(self.labelsStackView) } } private func openBalancesBlockchainActions( user: Account.Model, selectedTokens: (Network.Model.Token, Network.Model.Token, String)) -> [(Contract, BlockchainAction, Codable)] { let (_, tokenGet, certificateToken) = selectedTokens let tokensNames = tokens.map({ $0.symbol }) let missingBalancesOnAccount = (!tokensNames.contains(certificateToken) ? [certificateToken] : []) + (!tokensNames.contains(tokenGet.symbol) ? [tokenGet.symbol] : []) return missingBalancesOnAccount.map { token in let precicion = cashTokens.first(where: { $0.symbol == token })?.precision ?? 0 return Network.Model.Blockchain.OpenBalance(owner: user.username, symbol: "\(precicion),\(token)", ram_payer: user.username) }.map({ (Contract.cash, .open, $0) }) } private func generateTokensBlockchainActions( user: Account.Model, selectedTokens: (Network.Model.Token, Network.Model.Token, String)) -> [(Contract, BlockchainAction, Codable)] { let (tokenGive, tokenGet, _) = selectedTokens let quantity = (Decimal(youGiveTokenSelectorView.count) * (tokensIncomeCoef ?? 0.0)) .toCanonicalString(precision: tokenGive.precision, symbol: tokenGive.symbol) let data = Network.Model.Blockchain.Transfer(from: user.username, to: Network.servers.current.contract(.cash), quantity: quantity, memo: tokenGet.symbol) return [(contract: .list, action: .transfer, data: data)] } } extension CCCPControllerGenerateTokens: CCCPViewTokenSelectorDelegate { func stateChanged() { youGiveTokenSelectorView.notesSectionStackView.isHidden = true if let selectedToken = youGiveTokenSelectorView.selectedToken, let coef = tokensIncomeCoef { youGiveTokenSelectorView.titleLabel.text = L10n.Cccp.GenerateTokens .youGiveTitle(coef.toString(max: selectedToken.precision)) youGiveTokenSelectorView.coef = coef if selectedToken.amount < coef { youGiveTokenSelectorView.notesSectionStackView.isHidden = false } amountTextField.isHidden = false amountTextField.error = selectedToken.amount < Decimal(youGiveTokenSelectorView.count) * coef ? L10n.Cccp.IssueTokens.youGiveAmountError(selectedToken.symbol) : nil amountTextField.lzText = L10n.Cccp.IssueTokens.youGiveAmountDescription("\(coef)", selectedToken.symbol) tokenGetStackView.isHidden = false youGiveInfoLbl.updateText("\((Decimal(youGiveTokenSelectorView.count) * coef).toString(max: 8)) \(selectedToken.symbol)") } else { youGiveTokenSelectorView.titleLabel.text = L10n.Cccp.GenerateTokens.youGiveTitle("") youGiveTokenSelectorView.coef = nil } //Reset appearance youGetTokenSelectorView.notesSectionSubStackView.arrangedSubviews .forEach({ $0.removeFromSuperview() }) generateTokensButton.isEnabled = false guard let (_, tokenGet, certificateToken) = selectedTokens else { return } labelsStackView.isHidden = false //Cash token select section view appearance let cashTokensCount = Decimal(youGiveTokenSelectorView.count) * tokenGet.certCoef let labelCashToekns = CCCPLabelAddMoneyColored(amount: cashTokensCount, symbol: tokenGet.symbol) youGetTokenSelectorView.notesSectionSubStackView.addArrangedSubview(labelCashToekns) let labelGCashToekns = CCCPLabelAddMoneyColored(amount: Decimal(youGiveTokenSelectorView.count), symbol: certificateToken) youGetTokenSelectorView.notesSectionSubStackView.addArrangedSubview(labelGCashToekns) youGetInfoLbl.clearTexts() youGetInfoLbl.addText("\(cashTokensCount.toString(max: 5)) \(tokenGet.symbol)") youGetInfoLbl.addText("\(youGiveTokenSelectorView.count) \(certificateToken)") youGetInfoLbl.addText("\(youGiveTokenSelectorView.count) \(L10n.Cccp.Main.bill) \(certificateToken)") //Generate button appearance if youGiveTokenSelectorView.count > 0 && youGiveTokenSelectorView.count <= youGiveTokenSelectorView.maxTokens { generateTokensButton.isEnabled = true } } } extension CCCPControllerGenerateTokens: CommonTextFieldDelegate { func textFieldDidChange(_ textField: CommonTextField) { youGiveTokenSelectorView.count = Int(textField.value) ?? 0 } }