Files
2022-09-19 19:20:00 +03:00

351 lines
14 KiB
Swift

//
// 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()
})
}
}