// // SwapControllerMain.swift // Wallet // // Created by Saveliy Stavitsky on 3/1/21. // Copyright © 2021 AM. All rights reserved. // import UIKit import WalletFoundation //https://www.hackingwithswift.com/example-code/language/how-to-split-an-array-into-chunks extension Array { func chunked(into size: Int) -> [[Element]] { return stride(from: 0, to: count, by: size).map { Array(self[$0 ..< Swift.min($0 + size, count)]) } } } class SwapControllerMain: UIViewController { let service = Swap.Service.Pools() @IBOutlet private weak var tokenGiveView: CommonViewCard! @IBOutlet private weak var tokenGetStackView: UIStackView! @IBOutlet private weak var changeTokensButton: UIButton! @IBOutlet private weak var tokenGetView: CommonViewCard! @IBOutlet private weak var conversionRateStackView: UIStackView! @IBOutlet private weak var forwardConversionRate: UILabel! @IBOutlet private weak var backwardConversionRate: UILabel! @IBOutlet private weak var conversionFieldsStackView: UIStackView! @IBOutlet private weak var amount1TextField: CommonTextField! @IBOutlet private weak var amount2TextField: CommonTextField! @IBOutlet private weak var minimumTextField: CommonTextField! @IBOutlet private weak var routeContainerView: UIView! @IBOutlet private weak var routeStackView: UIStackView! @IBOutlet private weak var routeTokensStackView: UIStackView! @IBOutlet var swapButton: CommonButtonAction! override func viewDidLoad() { super.viewDidLoad() self.title = L10n.Swap.Main.title self.navigationItem.leftBarButtonItem = .pop(self) tabBarItem.title = L10n.Swap.Main.TabBar.title tokenGiveView.action = .cards(collection: nil) tokenGetView.action = .cards(collection: nil) for textField in [amount1TextField, amount2TextField, minimumTextField] { textField?.delegate = self textField?.keyboardType = .decimalPad } swapButton.isEnabled = false service.didUpdate = { [weak self] in self?.setupTokensViews() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) Accounts().isLocked = true service.tokenGive = nil service.tokenGet = nil setupTokensViews() service.fetch { Alert.error($0) } self.navigationController >>- { $0.navigationBar.isTranslucent = false $0.setNavigationBarHidden(false, animated: true) } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) Accounts().isLocked = false } func setupTokensViews() { if service.showPreselectError { service.showPreselectError = false Popup.show(title: L10n.Swap.Main.PreselectToken.errorTitle, text: L10n.Swap.Main.PreselectToken.errorText, submit: L10n.Common.Button.understand) } tokenGiveView.action = .cards( tokens: service.tokensFrom, selected: service.tokenGive, empty: .menu( image: Asset.commonToken.image.tinted(with: Asset.deepWater.color), title: L10n.Swap.Main.TokenGive.titleCard), { [weak self] in self?.service.tokenGive = self?.service.tokensFrom.object(menu: $0) self?.amount1TextField.precision = self?.service.tokensFrom.object(menu: $0)?.precision self?.setupTokensViews() }) tokenGetStackView.isHidden = service.tokenGive == nil tokenGetView.action = .cards( tokens: service.tokensTo, selected: service.tokenGet, empty: .menu( image: Asset.commonToken.image.tinted(with: Asset.deepWater.color), title: L10n.Swap.Main.TokenGet.titleCard), { [weak self] in self?.service.tokenGet = self?.service.tokensTo.object(menu: $0) self?.amount2TextField.precision = self?.service.tokensTo.object(menu: $0)?.precision self?.minimumTextField.precision = self?.service.tokensTo.object(menu: $0)?.precision self?.setupTokensViews() }) changeTokensButton.isHidden = service.tokenGive == nil || service.tokenGet == nil minimumTextField.isHidden = service.amountSend.isEmpty || service.amountReceive.isEmpty conversionRateStackView.isHidden = tokenGetStackView.isHidden || service.tokenGet == nil conversionFieldsStackView.isHidden = conversionRateStackView.isHidden routeContainerView.isHidden = service.tokenGive == nil || service.tokenGet == nil routeStackView.isHidden = service.tokenGive == nil || service.tokenGet == nil guard let tokenGive = service.tokenGive, let tokenGet = service.tokenGet else { return } let processAmount = service.amountSend.toOptionalDecimal() != nil ? service.processRate(amount: service.amountSend.toDecimal()) / service.amountSend.toDecimal() : service.processRate(amount: 1.0) forwardConversionRate.lzText = "\(Decimal(1).toString(precision: tokenGive)) ≈ \(processAmount.toString(precision: tokenGet))" backwardConversionRate.lzText = "(\(Decimal(1).toString(precision: tokenGet)) ≈ \((1 / processAmount).toString(precision: tokenGive)))" let giveImage = UIImageView(token: tokenGive) giveImage.addHeightWidthConstraints(height: 24, width: 24) amount1TextField.accessoryView = giveImage amount1TextField.value = service.amountSend amount1TextField.error = service.amountSendError let getImage = UIImageView(token: tokenGet) getImage.addHeightWidthConstraints(height: 24, width: 24) amount2TextField.accessoryView = getImage amount2TextField.value = service.amountReceive amount2TextField.error = service.amountReceiveError minimumTextField.value = service.amountMinimum // minimumTextField.error = service.amountMinimumShowError ? L10n.Swap.Main.Minimum.error : nil routeTokensStackView.arrangedSubviews.forEach({ $0.removeFromSuperview() }) for tokensChunk in service.route.chunked(into: (Int(UIScreen.main.bounds.width) - 32) / 85) { let stackViewLine = UIStackView(subviews: [], axis: .horizontal, distribution: .fill, alignment: .center, spacing: 0) for token in tokensChunk { let image = UIImageView(token: token) image.addHeightWidthConstraints(height: 32, width: 32) let label = UILabel() label.font = UIFont.font(style: .medium, size: 10) label.textColor = Asset.textCoal.color label.textAlignment = .center label.text = token.symbol label.addHeightWidthConstraints(height: 12, width: 65) let stack = UIStackView(subviews: [image, label], axis: .vertical, distribution: .fill, alignment: .center, spacing: 2) stackViewLine.addArrangedSubview(stack) let imageArrow = UIImageView(image: Asset.commonArrowRight.image) imageArrow.addHeightWidthConstraints(height: 20, width: 20) stackViewLine.addArrangedSubview(imageArrow) } routeTokensStackView.addArrangedSubview(stackViewLine) } (routeTokensStackView.arrangedSubviews.last as? UIStackView)? .arrangedSubviews.last?.removeFromSuperview() swapButton.isEnabled = !amount1TextField.value.isEmpty && !amount2TextField.value.isEmpty && service.amountSendError == nil && service.amountReceiveError == nil && service.amountSend.toDecimal() > 0 && service.amountReceive.toDecimal() > 0 // && (service.amountMinimum ?? 0) > 0 } @IBAction func changeTokensPressed(_ sender: Any) { if let tokenGive = service.tokenGive, let tokenGet = service.tokenGet { service.tokenGive = tokenGet amount1TextField.precision = tokenGet.precision service.tokenGet = tokenGive amount2TextField.precision = tokenGive.precision minimumTextField.precision = tokenGive.precision service.preselectTokenGive = nil setupTokensViews() } } @IBAction func swapPressed(_ sender: Any) { view.endEditing(true) guard let tokenGive = service.tokenGive, let tokenGet = service.tokenGet else { return } let amountSend = service.amountSend.toDecimal().toString(max: tokenGive) let amountReceive = service.amountReceive.toDecimal().toString(max: tokenGet) var views: [UIView] = [ .label( text: L10n.Swap.Main.Confirmation.description .attributed(style: .regular, size: 14, color: Asset.textCoal.color), numberOfLines: 0 ), .field( title: L10n.Swap.Main.Confirmation.give, text: amountSend ), .field( title: L10n.Swap.Main.Confirmation.get, text: amountReceive ) ] if let amountMinimum = service.amountMinimum.toOptionalDecimal() { views.append( .field( title: L10n.Swap.Main.Confirmation.minimum, titleStyle: "regular_16", titleColor: Asset.textPebble.color, text: amountMinimum.toString(max: tokenGet), textStyle: "regular_16", textColor: Asset.textPebble.color ) ) } Popup.show(title: L10n.Common.Title.confirmation, views: views, submit: L10n.Common.Button.confirm, in: self) { ctrl in AccountViewAuthorize.showGetPrivateKey(in: self) { [weak self] in Loader.show(in: ctrl) self?.service.submit(privateKey: $0) { [weak self] (result) in guard let self = self else { return } Loader.hide(in: ctrl) ctrl.dismiss(animated: true) switch result { case .success(let transactionId): self.showSuccessfulPopup(amountSend: amountSend, amountReceive: amountReceive, transactionId: transactionId, completion: { self.clear() self.service.tokenGet = self.service.getToken(tokenGet) self.service.tokenGive = self.service.getToken(tokenGive) self.service.fetch({ _ in }) }) case let .failure(error): Alert.notify(error, pop: self) } } } } } private func clear() { amount1TextField.value = .init() amount2TextField.value = .init() minimumTextField.value = .init() service.update(send: "") service.update(receive: "") service.update(minimum: "") } } extension SwapControllerMain: CommonTextFieldDelegate { func textField(_ textField: CommonTextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let newString = (textField.value as NSString).replacingCharacters(in: range, with: string) switch textField { case amount1TextField: if let precision = service.tokenGive?.precision, newString.precision > precision { return false } case amount2TextField: if let precision = service.tokenGet?.precision, newString.precision > precision { return false } case minimumTextField: if let precision = service.tokenGet?.precision, newString.precision > precision { return false } default: break } return true } func textFieldDidChange(_ textField: CommonTextField) { switch textField { case amount1TextField: textField.value = textField.value.replacingOccurrences(of: ",", with: ".") let newPosition = textField.textField.endOfDocument textField.textField.selectedTextRange = textField.textField.textRange(from: newPosition, to: newPosition) service.update(send: textField.value) case amount2TextField: textField.value = textField.value.replacingOccurrences(of: ",", with: ".") let newPosition = textField.textField.endOfDocument textField.textField.selectedTextRange = textField.textField.textRange(from: newPosition, to: newPosition) service.update(receive: textField.value) case minimumTextField: textField.value = textField.value.replacingOccurrences(of: ",", with: ".") let newPosition = textField.textField.endOfDocument textField.textField.selectedTextRange = textField.textField.textRange(from: newPosition, to: newPosition) service.update(minimum: textField.value) default: break } } } extension SwapControllerMain { fileprivate func showSuccessfulPopup(amountSend: String, amountReceive: String, transactionId: String, completion: @escaping () -> Void) { let views: [UIView] = [ .field( title: L10n.Swap.Main.Success.give, text: amountSend ), .field( title: L10n.Swap.Main.Success.get, text: amountReceive ), .field( title: L10n.Swap.Main.Success.id, text: transactionId, rightButtonAvailbale: true, rightButtonImage: Asset.commonCopy.image, rightButtonAction: { Alert.copy(transactionId) } ) ] Popup.show(title: L10n.Swap.Main.Success.title, views: views, submit: L10n.Common.Button.checkOnBloksIO, submitStyle: .outline, cancel: L10n.Common.Button.done, in: self, { ctrl in guard let checkTransactionUrl = URL(transactionId: transactionId) else { return } UIApplication.shared.open(checkTransactionUrl, options: [:], completionHandler: nil) }, { completion() }) } }