|
|
@ -1,4 +1,5 @@ |
|
|
|
// Copyright SIX DAY LLC. All rights reserved. |
|
|
|
// Copyright SIX DAY LLC. All rights reserved. |
|
|
|
|
|
|
|
// Copyright © 2018 Stormbird PTE. LTD. |
|
|
|
|
|
|
|
|
|
|
|
import Foundation |
|
|
|
import Foundation |
|
|
|
import UIKit |
|
|
|
import UIKit |
|
|
@ -8,6 +9,7 @@ import APIKit |
|
|
|
import QRCodeReaderViewController |
|
|
|
import QRCodeReaderViewController |
|
|
|
import BigInt |
|
|
|
import BigInt |
|
|
|
import TrustKeystore |
|
|
|
import TrustKeystore |
|
|
|
|
|
|
|
import MBProgressHUD |
|
|
|
|
|
|
|
|
|
|
|
protocol SendViewControllerDelegate: class { |
|
|
|
protocol SendViewControllerDelegate: class { |
|
|
|
func didPressConfirm( |
|
|
|
func didPressConfirm( |
|
|
@ -17,20 +19,43 @@ protocol SendViewControllerDelegate: class { |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class SendViewController: FormViewController { |
|
|
|
class SendViewController: UIViewController { |
|
|
|
private lazy var viewModel: SendViewModel = { |
|
|
|
//roundedBackground is used to achieve the top 2 rounded corners-only effect since maskedCorners to not round bottom corners is not available in iOS 10 |
|
|
|
return SendViewModel(transferType: self.transferType, |
|
|
|
let roundedBackground = UIView() |
|
|
|
config: Config(), |
|
|
|
let header = SendHeaderView() |
|
|
|
ticketHolders: self.ticketHolders) |
|
|
|
let targetAddressTextField = UITextField() |
|
|
|
|
|
|
|
let amountTextField = UITextField() |
|
|
|
|
|
|
|
let alternativeAmountLabel = UILabel() |
|
|
|
|
|
|
|
let targetAddressLabel = UILabel() |
|
|
|
|
|
|
|
let amountLabel = UILabel() |
|
|
|
|
|
|
|
let myAddressContainer = UIView() |
|
|
|
|
|
|
|
let myAddressLabelLabel = UILabel() |
|
|
|
|
|
|
|
let myAddressLabel: UILabel = { |
|
|
|
|
|
|
|
let label = UILabel(frame: .zero) |
|
|
|
|
|
|
|
label.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
label.numberOfLines = 0 |
|
|
|
|
|
|
|
label.textAlignment = .center |
|
|
|
|
|
|
|
label.minimumScaleFactor = 0.5 |
|
|
|
|
|
|
|
label.adjustsFontSizeToFitWidth = true |
|
|
|
|
|
|
|
return label |
|
|
|
}() |
|
|
|
}() |
|
|
|
weak var delegate: SendViewControllerDelegate? |
|
|
|
let copyButton: UIButton = { |
|
|
|
|
|
|
|
let button = Button(size: .normal, style: .border) |
|
|
|
|
|
|
|
button.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
button.addTarget(self, action: #selector(copyAddress), for: .touchUpInside) |
|
|
|
|
|
|
|
return button |
|
|
|
|
|
|
|
}() |
|
|
|
|
|
|
|
let imageView: UIImageView = { |
|
|
|
|
|
|
|
let imageView = UIImageView() |
|
|
|
|
|
|
|
imageView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
return imageView |
|
|
|
|
|
|
|
}() |
|
|
|
|
|
|
|
let nextButton = UIButton(type: .system) |
|
|
|
|
|
|
|
|
|
|
|
struct Values { |
|
|
|
var viewModel: SendViewModel! |
|
|
|
static let address = "address" |
|
|
|
var headerViewModel = SendHeaderViewViewModel() |
|
|
|
static let amount = "amount" |
|
|
|
var balanceViewModel: BalanceBaseViewModel? |
|
|
|
static let existingTicketIds = "existingTicketIds" |
|
|
|
weak var delegate: SendViewControllerDelegate? |
|
|
|
static let ticketIdsToSend = "ticketIdsToSend" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct Pair { |
|
|
|
struct Pair { |
|
|
|
let left: String |
|
|
|
let left: String |
|
|
@ -46,17 +71,7 @@ class SendViewController: FormViewController { |
|
|
|
let account: Account |
|
|
|
let account: Account |
|
|
|
let transferType: TransferType |
|
|
|
let transferType: TransferType |
|
|
|
let storage: TokensDataStore |
|
|
|
let storage: TokensDataStore |
|
|
|
let ticketHolders: [TicketHolder]! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var addressRow: TextFloatLabelRow? { |
|
|
|
|
|
|
|
return form.rowBy(tag: Values.address) as? TextFloatLabelRow |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var amountRow: TextFloatLabelRow? { |
|
|
|
|
|
|
|
return form.rowBy(tag: Values.amount) as? TextFloatLabelRow |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var ticketIdsRow: TextFloatLabelRow? { |
|
|
|
|
|
|
|
return form.rowBy(tag: Values.ticketIdsToSend) as? TextFloatLabelRow |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
private var allowedCharacters: String = { |
|
|
|
private var allowedCharacters: String = { |
|
|
|
let decimalSeparator = Locale.current.decimalSeparator ?? "." |
|
|
|
let decimalSeparator = Locale.current.decimalSeparator ?? "." |
|
|
|
return "0123456789" + decimalSeparator |
|
|
|
return "0123456789" + decimalSeparator |
|
|
@ -69,122 +84,233 @@ class SendViewController: FormViewController { |
|
|
|
lazy var decimalFormatter: DecimalFormatter = { |
|
|
|
lazy var decimalFormatter: DecimalFormatter = { |
|
|
|
return DecimalFormatter() |
|
|
|
return DecimalFormatter() |
|
|
|
}() |
|
|
|
}() |
|
|
|
lazy var stringFormatter: StringFormatter = { |
|
|
|
|
|
|
|
return StringFormatter() |
|
|
|
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init( |
|
|
|
init( |
|
|
|
session: WalletSession, |
|
|
|
session: WalletSession, |
|
|
|
storage: TokensDataStore, |
|
|
|
storage: TokensDataStore, |
|
|
|
account: Account, |
|
|
|
account: Account, |
|
|
|
transferType: TransferType = .ether(destination: .none), |
|
|
|
transferType: TransferType = .ether(destination: .none) |
|
|
|
ticketHolders: [TicketHolder] = [] |
|
|
|
|
|
|
|
) { |
|
|
|
) { |
|
|
|
self.session = session |
|
|
|
self.session = session |
|
|
|
self.account = account |
|
|
|
self.account = account |
|
|
|
self.transferType = transferType |
|
|
|
self.transferType = transferType |
|
|
|
self.storage = storage |
|
|
|
self.storage = storage |
|
|
|
self.ticketHolders = ticketHolders |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
super.init(nibName: nil, bundle: nil) |
|
|
|
super.init(nibName: nil, bundle: nil) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configureBalanceViewModel() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
roundedBackground.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
roundedBackground.backgroundColor = Colors.appWhite |
|
|
|
|
|
|
|
roundedBackground.cornerRadius = 20 |
|
|
|
|
|
|
|
view.addSubview(roundedBackground) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
targetAddressTextField.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
targetAddressTextField.delegate = self |
|
|
|
|
|
|
|
targetAddressTextField.returnKeyType = .next |
|
|
|
|
|
|
|
targetAddressTextField.leftViewMode = .always |
|
|
|
|
|
|
|
targetAddressTextField.rightViewMode = .always |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
amountTextField.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
amountTextField.delegate = self |
|
|
|
|
|
|
|
amountTextField.keyboardType = .decimalPad |
|
|
|
|
|
|
|
amountTextField.leftViewMode = .always |
|
|
|
|
|
|
|
amountTextField.rightViewMode = .always |
|
|
|
|
|
|
|
amountTextField.inputAccessoryView = makeToolbarWithDoneButton() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
myAddressContainer.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let myAddressContainerCol0 = UIStackView(arrangedSubviews: [ |
|
|
|
|
|
|
|
myAddressLabelLabel, |
|
|
|
|
|
|
|
.spacer(height: 10), |
|
|
|
|
|
|
|
myAddressLabel, |
|
|
|
|
|
|
|
.spacer(height: 10), |
|
|
|
|
|
|
|
copyButton, |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
myAddressContainerCol0.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
myAddressContainerCol0.axis = .vertical |
|
|
|
|
|
|
|
myAddressContainerCol0.spacing = 0 |
|
|
|
|
|
|
|
myAddressContainerCol0.distribution = .fill |
|
|
|
|
|
|
|
myAddressContainerCol0.alignment = .center |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let myAddressContainerStackView = UIStackView(arrangedSubviews: [ |
|
|
|
|
|
|
|
myAddressContainerCol0, |
|
|
|
|
|
|
|
.spacerWidth(20), |
|
|
|
|
|
|
|
imageView, |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
myAddressContainerStackView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
myAddressContainerStackView.axis = .horizontal |
|
|
|
|
|
|
|
myAddressContainerStackView.spacing = 0 |
|
|
|
|
|
|
|
myAddressContainerStackView.distribution = .fill |
|
|
|
|
|
|
|
myAddressContainerStackView.alignment = .center |
|
|
|
|
|
|
|
myAddressContainer.addSubview(myAddressContainerStackView) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nextButton.setTitle(R.string.localizable.aWalletTicketTokenTransferButtonTitle(), for: .normal) |
|
|
|
|
|
|
|
nextButton.addTarget(self, action: #selector(send), for: .touchUpInside) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let buttonsStackView = UIStackView(arrangedSubviews: [nextButton]) |
|
|
|
|
|
|
|
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
buttonsStackView.axis = .horizontal |
|
|
|
|
|
|
|
buttonsStackView.spacing = 0 |
|
|
|
|
|
|
|
buttonsStackView.distribution = .fillEqually |
|
|
|
|
|
|
|
buttonsStackView.setContentHuggingPriority(.required, for: .horizontal) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let stackView = UIStackView(arrangedSubviews: [ |
|
|
|
|
|
|
|
header, |
|
|
|
|
|
|
|
.spacer(height: ScreenChecker().isNarrowScreen() ? 7: 20), |
|
|
|
|
|
|
|
targetAddressLabel, |
|
|
|
|
|
|
|
.spacer(height: ScreenChecker().isNarrowScreen() ? 2 : 4), |
|
|
|
|
|
|
|
targetAddressTextField, |
|
|
|
|
|
|
|
.spacer(height: ScreenChecker().isNarrowScreen() ? 7 : 14), |
|
|
|
|
|
|
|
amountLabel, |
|
|
|
|
|
|
|
.spacer(height: ScreenChecker().isNarrowScreen() ? 2 : 4), |
|
|
|
|
|
|
|
amountTextField, |
|
|
|
|
|
|
|
alternativeAmountLabel, |
|
|
|
|
|
|
|
.spacer(height: ScreenChecker().isNarrowScreen() ? 10: 20), |
|
|
|
|
|
|
|
myAddressContainer, |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
stackView.axis = .vertical |
|
|
|
|
|
|
|
stackView.spacing = 0 |
|
|
|
|
|
|
|
stackView.distribution = .fill |
|
|
|
|
|
|
|
stackView.alignment = .center |
|
|
|
|
|
|
|
roundedBackground.addSubview(stackView) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let marginToHideBottomRoundedCorners = CGFloat(30) |
|
|
|
|
|
|
|
let footerBar = UIView() |
|
|
|
|
|
|
|
footerBar.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
footerBar.backgroundColor = Colors.appHighlightGreen |
|
|
|
|
|
|
|
roundedBackground.addSubview(footerBar) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let buttonsHeight = CGFloat(60) |
|
|
|
|
|
|
|
footerBar.addSubview(buttonsStackView) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSLayoutConstraint.activate([ |
|
|
|
|
|
|
|
header.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30), |
|
|
|
|
|
|
|
header.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
targetAddressTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30), |
|
|
|
|
|
|
|
targetAddressTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30), |
|
|
|
|
|
|
|
targetAddressTextField.heightAnchor.constraint(equalToConstant: ScreenChecker().isNarrowScreen() ? 30 : 50), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
amountTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30), |
|
|
|
|
|
|
|
amountTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30), |
|
|
|
|
|
|
|
amountTextField.heightAnchor.constraint(equalToConstant: ScreenChecker().isNarrowScreen() ? 30 : 50), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
myAddressContainerStackView.leadingAnchor.constraint(equalTo: myAddressContainer.leadingAnchor, constant: 20), |
|
|
|
|
|
|
|
myAddressContainerStackView.trailingAnchor.constraint(equalTo: myAddressContainer.trailingAnchor, constant: -20), |
|
|
|
|
|
|
|
myAddressContainerStackView.topAnchor.constraint(equalTo: myAddressContainer.topAnchor, constant: ScreenChecker().isNarrowScreen() ? 10 : 20), |
|
|
|
|
|
|
|
myAddressContainerStackView.bottomAnchor.constraint(equalTo: myAddressContainer.bottomAnchor, constant: ScreenChecker().isNarrowScreen() ? -10 : -20), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
myAddressContainer.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30), |
|
|
|
|
|
|
|
myAddressContainer.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
roundedBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor), |
|
|
|
|
|
|
|
roundedBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor), |
|
|
|
|
|
|
|
roundedBackground.topAnchor.constraint(equalTo: view.topAnchor), |
|
|
|
|
|
|
|
roundedBackground.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: marginToHideBottomRoundedCorners), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
imageView.widthAnchor.constraint(equalTo: myAddressContainerStackView.widthAnchor, multiplier: 0.5, constant: 10), |
|
|
|
|
|
|
|
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor), |
|
|
|
|
|
|
|
stackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor), |
|
|
|
|
|
|
|
stackView.topAnchor.constraint(equalTo: roundedBackground.topAnchor), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
buttonsStackView.leadingAnchor.constraint(equalTo: footerBar.leadingAnchor), |
|
|
|
|
|
|
|
buttonsStackView.trailingAnchor.constraint(equalTo: footerBar.trailingAnchor), |
|
|
|
|
|
|
|
buttonsStackView.topAnchor.constraint(equalTo: footerBar.topAnchor), |
|
|
|
|
|
|
|
buttonsStackView.heightAnchor.constraint(equalToConstant: buttonsHeight), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), |
|
|
|
|
|
|
|
footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), |
|
|
|
|
|
|
|
footerBar.heightAnchor.constraint(equalToConstant: buttonsHeight), |
|
|
|
|
|
|
|
footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor), |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
storage.updatePrices() |
|
|
|
storage.updatePrices() |
|
|
|
getGasPrice() |
|
|
|
getGasPrice() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if viewModel.isStormBird { |
|
|
|
@objc func closeKeyboard() { |
|
|
|
title = viewModel.title |
|
|
|
view.endEditing(true) |
|
|
|
} else { |
|
|
|
} |
|
|
|
navigationItem.titleView = BalanceTitleView.make(from: self.session, transferType) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
view.backgroundColor = viewModel.backgroundColor |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let recipientRightView = FieldAppereance.addressFieldRightView( |
|
|
|
|
|
|
|
pasteAction: { [unowned self] in self.pasteAction() }, |
|
|
|
|
|
|
|
qrAction: { [unowned self] in self.openReader() } |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let maxButton = Button(size: .normal, style: .borderless) |
|
|
|
|
|
|
|
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) |
|
|
|
func configure(viewModel: SendViewModel) { |
|
|
|
fiatButton.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
let firstConfigure = self.viewModel == nil |
|
|
|
fiatButton.setTitle(currentPair.right, for: .normal) |
|
|
|
self.viewModel = viewModel |
|
|
|
fiatButton.addTarget(self, action: #selector(fiatAction), for: .touchUpInside) |
|
|
|
|
|
|
|
fiatButton.isHidden = isFiatViewHidden() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let amountRightView = UIStackView(arrangedSubviews: [ |
|
|
|
if firstConfigure { |
|
|
|
fiatButton, |
|
|
|
//Not good to rely on viewModel here on firstConfigure, which means if we change the padding on subsequent calls (which will probably never happen), it wouldn't be reflected. Unfortunately this needs to be here, otherwise while typing in the amount text field, the left and right views will move out of the text field momentarily |
|
|
|
]) |
|
|
|
amountTextField.leftView = .spacerWidth(viewModel.textFieldHorizontalPadding) |
|
|
|
|
|
|
|
amountTextField.rightView = makeAmountRightView() |
|
|
|
|
|
|
|
targetAddressTextField.leftView = .spacerWidth(viewModel.textFieldHorizontalPadding) |
|
|
|
|
|
|
|
targetAddressTextField.rightView = makeTargetAddressRightView() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
amountRightView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
changeQRCode(value: 0) |
|
|
|
amountRightView.distribution = .equalSpacing |
|
|
|
|
|
|
|
amountRightView.spacing = 1 |
|
|
|
|
|
|
|
amountRightView.axis = .horizontal |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if viewModel.isStormBird { |
|
|
|
view.backgroundColor = viewModel.backgroundColor |
|
|
|
form += [Section(viewModel.formHeaderTitle) |
|
|
|
|
|
|
|
<<< TextAreaRow(Values.existingTicketIds) { |
|
|
|
|
|
|
|
$0.textAreaHeight = .dynamic(initialTextViewHeight: 44) |
|
|
|
|
|
|
|
$0.value = viewModel.ticketNumbers |
|
|
|
|
|
|
|
}.cellUpdate { cell, _ in |
|
|
|
|
|
|
|
cell.isUserInteractionEnabled = false |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
form += [Section(footer: formFooterText()) |
|
|
|
header.configure(viewModel: headerViewModel) |
|
|
|
<<< AppFormAppearance.textFieldFloat(tag: Values.address) { |
|
|
|
|
|
|
|
$0.add(rule: EthereumAddressRule()) |
|
|
|
targetAddressTextField.textColor = viewModel.textFieldTextColor |
|
|
|
$0.validationOptions = .validatesOnDemand |
|
|
|
targetAddressTextField.font = viewModel.textFieldFont |
|
|
|
}.cellUpdate { cell, _ in |
|
|
|
targetAddressTextField.layer.borderColor = viewModel.textFieldBorderColor.cgColor |
|
|
|
cell.textField.textAlignment = .left |
|
|
|
targetAddressTextField.layer.borderWidth = viewModel.textFieldBorderWidth |
|
|
|
cell.textField.placeholder = NSLocalizedString("send.recipientAddress.textField.placeholder", value: "Recipient Address", comment: "") |
|
|
|
|
|
|
|
cell.textField.rightView = recipientRightView |
|
|
|
//targetAddressLabel.text = R.string.localizable.aSendRecipientAddressTitle() |
|
|
|
cell.textField.rightViewMode = .always |
|
|
|
targetAddressLabel.font = viewModel.textFieldsLabelFont |
|
|
|
cell.textField.accessibilityIdentifier = "amount-field" |
|
|
|
targetAddressLabel.textColor = viewModel.textFieldsLabelTextColor |
|
|
|
} |
|
|
|
|
|
|
|
<<< AppFormAppearance.textFieldFloat(tag: Values.amount) { |
|
|
|
//amountLabel.text = R.string.localizable.aSendRecipientAmountTitle() |
|
|
|
$0.add(rule: RuleClosure<String> { [weak self] rowValue in |
|
|
|
amountLabel.font = viewModel.textFieldsLabelFont |
|
|
|
return !(self?.viewModel.isStormBird)! && (rowValue == nil || rowValue!.isEmpty) ? ValidationError(msg: "Field required!") : nil |
|
|
|
amountLabel.textColor = viewModel.textFieldsLabelTextColor |
|
|
|
}) |
|
|
|
|
|
|
|
$0.validationOptions = .validatesOnDemand |
|
|
|
amountTextField.textColor = viewModel.textFieldTextColor |
|
|
|
$0.hidden = Condition(booleanLiteral: self.viewModel.isStormBird) |
|
|
|
amountTextField.font = viewModel.textFieldFont |
|
|
|
}.cellUpdate { [weak self] cell, _ in |
|
|
|
amountTextField.layer.borderColor = viewModel.textFieldBorderColor.cgColor |
|
|
|
cell.textField.isCopyPasteDisabled = true |
|
|
|
amountTextField.layer.borderWidth = viewModel.textFieldBorderWidth |
|
|
|
cell.textField.textAlignment = .left |
|
|
|
|
|
|
|
cell.textField.delegate = self |
|
|
|
alternativeAmountLabel.numberOfLines = 0 |
|
|
|
cell.textField.placeholder = "\(self?.currentPair.left ?? "") " + NSLocalizedString("send.amount.textField.placeholder", value: "Amount", comment: "") |
|
|
|
alternativeAmountLabel.textColor = viewModel.alternativeAmountColor |
|
|
|
cell.textField.keyboardType = .decimalPad |
|
|
|
alternativeAmountLabel.font = viewModel.alternativeAmountFont |
|
|
|
cell.textField.rightView = amountRightView |
|
|
|
alternativeAmountLabel.textAlignment = .center |
|
|
|
cell.textField.rightViewMode = .always |
|
|
|
alternativeAmountLabel.text = viewModel.alternativeAmountText |
|
|
|
} |
|
|
|
alternativeAmountLabel.isHidden = !viewModel.showAlternativeAmount |
|
|
|
<<< AppFormAppearance.textFieldFloat(tag: Values.ticketIdsToSend) { |
|
|
|
|
|
|
|
$0.add(rule: RuleClosure<String> { [weak self] rowValue in |
|
|
|
//myAddressLabelLabel.text = R.string.localizable.aSendSenderAddressTitle() |
|
|
|
if (self?.viewModel.isStormBird)! { |
|
|
|
myAddressLabelLabel.font = viewModel.textFieldsLabelFont |
|
|
|
if !(self?.ticketIdsValidated())! { |
|
|
|
myAddressLabelLabel.textColor = viewModel.textFieldsLabelTextColor |
|
|
|
return ValidationError(msg: "Please enter valid ticket IDs!") |
|
|
|
|
|
|
|
} |
|
|
|
myAddressLabel.textColor = viewModel.myAddressTextColor |
|
|
|
} |
|
|
|
myAddressLabel.font = viewModel.addressFont |
|
|
|
return nil |
|
|
|
myAddressLabel.text = viewModel.myAddressText |
|
|
|
}) |
|
|
|
|
|
|
|
$0.validationOptions = .validatesOnDemand |
|
|
|
copyButton.titleLabel?.font = viewModel.copyAddressButtonFont |
|
|
|
$0.hidden = Condition(booleanLiteral: !self.viewModel.isStormBird) |
|
|
|
copyButton.setTitle(" \(viewModel.copyAddressButtonTitle) ", for: .normal) |
|
|
|
}.cellUpdate { cell, _ in |
|
|
|
copyButton.setTitleColor(viewModel.copyAddressButtonTitleColor, for: .normal) |
|
|
|
cell.textField.isCopyPasteDisabled = true |
|
|
|
copyButton.backgroundColor = viewModel.copyAddressButtonBackgroundColor |
|
|
|
cell.textField.textAlignment = .left |
|
|
|
|
|
|
|
cell.textField.placeholder = NSLocalizedString("send.amount.textField.ticketids", value: "Enter Ticket IDs", comment: "") |
|
|
|
myAddressContainer.borderColor = viewModel.myAddressBorderColor |
|
|
|
cell.textField.keyboardType = .numbersAndPunctuation |
|
|
|
myAddressContainer.borderWidth = viewModel.myAddressBorderWidth |
|
|
|
}, |
|
|
|
myAddressContainer.cornerRadius = 20 |
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
nextButton.setTitleColor(viewModel.buttonTitleColor, for: .normal) |
|
|
|
|
|
|
|
nextButton.backgroundColor = viewModel.buttonBackgroundColor |
|
|
|
|
|
|
|
nextButton.titleLabel?.font = viewModel.buttonFont |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override func viewDidLayoutSubviews() { |
|
|
|
|
|
|
|
super.viewDidLayoutSubviews() |
|
|
|
|
|
|
|
roundCornersBasedOnHeight() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
override func viewWillAppear(_ animated: Bool) { |
|
|
|
private func roundCornersBasedOnHeight() { |
|
|
|
super.viewWillAppear(animated) |
|
|
|
targetAddressTextField.layer.cornerRadius = targetAddressTextField.frame.size.height / 2 |
|
|
|
self.navigationController?.applyTintAdjustment() |
|
|
|
amountTextField.layer.cornerRadius = amountTextField.frame.size.height / 2 |
|
|
|
|
|
|
|
copyButton.cornerRadius = copyButton.frame.size.height / 2 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func getGasPrice() { |
|
|
|
func getGasPrice() { |
|
|
@ -198,23 +324,11 @@ class SendViewController: FormViewController { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func clear() { |
|
|
|
|
|
|
|
let fields = [addressRow, amountRow, ticketIdsRow] |
|
|
|
|
|
|
|
for field in fields { |
|
|
|
|
|
|
|
field?.value = "" |
|
|
|
|
|
|
|
field?.reload() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@objc func send() { |
|
|
|
@objc func send() { |
|
|
|
let errors = form.validate() |
|
|
|
let addressString = targetAddressTextField.text?.trimmed ?? "" |
|
|
|
guard errors.isEmpty else { |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let addressString = addressRow?.value?.trimmed ?? "" |
|
|
|
|
|
|
|
var amountString = "" |
|
|
|
var amountString = "" |
|
|
|
if self.currentPair.left == viewModel.symbol { |
|
|
|
if self.currentPair.left == viewModel.symbol { |
|
|
|
amountString = amountRow?.value?.trimmed ?? "" |
|
|
|
amountString = amountTextField.text?.trimmed ?? "" |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
guard let formatedValue = decimalFormatter.string(from: NSNumber(value: self.pairValue)) else { |
|
|
|
guard let formatedValue = decimalFormatter.string(from: NSNumber(value: self.pairValue)) else { |
|
|
|
return displayError(error: SendInputErrors.wrongInput) |
|
|
|
return displayError(error: SendInputErrors.wrongInput) |
|
|
@ -252,7 +366,7 @@ class SendViewController: FormViewController { |
|
|
|
r: .none, |
|
|
|
r: .none, |
|
|
|
s: .none, |
|
|
|
s: .none, |
|
|
|
expiry: .none, |
|
|
|
expiry: .none, |
|
|
|
indices: viewModel.isStormBird ? getIndiciesFromUI() : .none |
|
|
|
indices: .none |
|
|
|
) |
|
|
|
) |
|
|
|
self.delegate?.didPressConfirm(transaction: transaction, transferType: transferType, in: self) |
|
|
|
self.delegate?.didPressConfirm(transaction: transaction, transferType: transferType, in: self) |
|
|
|
} |
|
|
|
} |
|
|
@ -271,28 +385,24 @@ class SendViewController: FormViewController { |
|
|
|
guard CryptoAddressValidator.isValidAddress(value) else { |
|
|
|
guard CryptoAddressValidator.isValidAddress(value) else { |
|
|
|
return displayError(error: Errors.invalidAddress) |
|
|
|
return displayError(error: Errors.invalidAddress) |
|
|
|
} |
|
|
|
} |
|
|
|
addressRow?.value = "0x99f05a668119d8938d79f85add73c9ab8ff719b1" |
|
|
|
targetAddressTextField.text = value |
|
|
|
addressRow?.reload() |
|
|
|
|
|
|
|
activateAmountView() |
|
|
|
activateAmountView() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@objc func useMaxAmount() { |
|
|
|
|
|
|
|
guard let value = session.balance?.amountFull else { |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
amountRow?.value = value |
|
|
|
|
|
|
|
amountRow?.reload() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@objc func fiatAction(sender: UIButton) { |
|
|
|
@objc func fiatAction(sender: UIButton) { |
|
|
|
let swappedPair = currentPair.swapPair() |
|
|
|
let swappedPair = currentPair.swapPair() |
|
|
|
//New pair for future calculation we should swap pair each time we press fiat button. |
|
|
|
//New pair for future calculation we should swap pair each time we press fiat button. |
|
|
|
self.currentPair = swappedPair |
|
|
|
self.currentPair = swappedPair |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if var viewModel = viewModel { |
|
|
|
|
|
|
|
viewModel.currentPair = currentPair |
|
|
|
|
|
|
|
viewModel.pairValue = 0 |
|
|
|
|
|
|
|
configure(viewModel: viewModel) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//Update button title. |
|
|
|
//Update button title. |
|
|
|
sender.setTitle(currentPair.right, for: .normal) |
|
|
|
sender.setTitle(currentPair.left, for: .normal) |
|
|
|
//Reset amountRow value. |
|
|
|
amountTextField.text = nil |
|
|
|
amountRow?.value = nil |
|
|
|
|
|
|
|
amountRow?.reload() |
|
|
|
|
|
|
|
//Reset pair value. |
|
|
|
//Reset pair value. |
|
|
|
pairValue = 0.0 |
|
|
|
pairValue = 0.0 |
|
|
|
//Update section. |
|
|
|
//Update section. |
|
|
@ -301,8 +411,17 @@ class SendViewController: FormViewController { |
|
|
|
activateAmountView() |
|
|
|
activateAmountView() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@objc func copyAddress() { |
|
|
|
|
|
|
|
UIPasteboard.general.string = viewModel.myAddressText |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let hud = MBProgressHUD.showAdded(to: view, animated: true) |
|
|
|
|
|
|
|
hud.mode = .text |
|
|
|
|
|
|
|
hud.label.text = viewModel.addressCopiedText |
|
|
|
|
|
|
|
hud.hide(animated: true, afterDelay: 1.5) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func activateAmountView() { |
|
|
|
func activateAmountView() { |
|
|
|
amountRow?.cell.textField.becomeFirstResponder() |
|
|
|
amountTextField.becomeFirstResponder() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
required init?(coder aDecoder: NSCoder) { |
|
|
|
required init?(coder aDecoder: NSCoder) { |
|
|
@ -310,87 +429,165 @@ class SendViewController: FormViewController { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func updatePriceSection() { |
|
|
|
private func updatePriceSection() { |
|
|
|
//Update section only if fiat view is visible. |
|
|
|
guard viewModel.showAlternativeAmount else { |
|
|
|
guard !isFiatViewHidden() else { |
|
|
|
|
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
//We use this section update to prevent update of the all section including cells. |
|
|
|
|
|
|
|
UIView.setAnimationsEnabled(false) |
|
|
|
if var viewModel = viewModel { |
|
|
|
tableView.beginUpdates() |
|
|
|
viewModel.pairValue = pairValue |
|
|
|
let footerSectionIndex: Int |
|
|
|
configure(viewModel: viewModel) |
|
|
|
if viewModel.isStormBird { |
|
|
|
|
|
|
|
footerSectionIndex = 1 |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
footerSectionIndex = 0 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if let containerView = tableView.footerView(forSection: footerSectionIndex) { |
|
|
|
|
|
|
|
containerView.textLabel!.text = valueOfPairRepresantetion() |
|
|
|
|
|
|
|
containerView.sizeToFit() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
tableView.endUpdates() |
|
|
|
|
|
|
|
UIView.setAnimationsEnabled(true) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func updatePairPrice(with amount: Double) { |
|
|
|
private func updatePairPrice(with amount: Double) { |
|
|
|
guard let rates = storage.tickers, let currentTokenInfo = rates[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price) else { |
|
|
|
guard let rates = storage.tickers, let currentTokenInfo = rates[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price) else { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
if self.currentPair.left == viewModel.symbol { |
|
|
|
if currentPair.left == viewModel.symbol { |
|
|
|
pairValue = amount * price |
|
|
|
pairValue = amount * price |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
pairValue = amount / price |
|
|
|
pairValue = amount / price |
|
|
|
} |
|
|
|
} |
|
|
|
self.updatePriceSection() |
|
|
|
updatePriceSection() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func isFiatViewHidden() -> Bool { |
|
|
|
private func addressTextFieldChanged(in range: NSRange, to string: String) -> Bool { |
|
|
|
guard let currentTokenInfo = storage.tickers?[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price), price > 0 else { |
|
|
|
return true |
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func formFooterText() -> String { |
|
|
|
private func amountTextFieldChanged(in range: NSRange, to string: String) -> Bool { |
|
|
|
return isFiatViewHidden() ? "" : valueOfPairRepresantetion() |
|
|
|
guard let input = amountTextField.text else { |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//In this step we validate only allowed characters it is because of the iPad keyboard. |
|
|
|
|
|
|
|
let characterSet = NSCharacterSet(charactersIn: allowedCharacters).inverted |
|
|
|
|
|
|
|
let separatedChars = string.components(separatedBy: characterSet) |
|
|
|
|
|
|
|
let filteredNumbersAndSeparator = separatedChars.joined(separator: "") |
|
|
|
|
|
|
|
if string != filteredNumbersAndSeparator { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//This is required to prevent user from input of numbers like 1.000.25 or 1,000,25. |
|
|
|
|
|
|
|
if string == "," || string == "." || string == "'" { |
|
|
|
|
|
|
|
return !input.contains(string) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let text = (input as NSString).replacingCharacters(in: range, with: string) |
|
|
|
|
|
|
|
guard let amount = decimalFormatter.number(from: text) else { |
|
|
|
|
|
|
|
//Should be done in another way. |
|
|
|
|
|
|
|
pairValue = 0.0 |
|
|
|
|
|
|
|
updatePriceSection() |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
updatePairPrice(with: amount.doubleValue) |
|
|
|
|
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func getTicket(for id: UInt16) -> Ticket? { |
|
|
|
private func changeQRCode(value: Int) { |
|
|
|
let tickets = ticketHolders.flatMap { $0.tickets } |
|
|
|
if let viewModel = viewModel { |
|
|
|
let filteredTickets = tickets.filter { UInt16($0.id, radix: 16)! == id } |
|
|
|
let string = viewModel.myAddressText |
|
|
|
return filteredTickets[Int(id)] |
|
|
|
DispatchQueue.global(qos: .background).async { |
|
|
|
|
|
|
|
// EIP67 format not being used much yet, use hex value for now |
|
|
|
|
|
|
|
// let string = "ethereum:\(account.address.address)?value=\(value)" |
|
|
|
|
|
|
|
let image = self.generateQRCode(from: string) |
|
|
|
|
|
|
|
DispatchQueue.main.async { |
|
|
|
|
|
|
|
self.imageView.image = image |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func isTicketExisting(for id: UInt16) -> Bool { |
|
|
|
|
|
|
|
return getTicket(for: id) != nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func getTicketIds() -> [String] { |
|
|
|
// private func getTicket(for id: UInt16) -> Ticket? { |
|
|
|
return (ticketIdsRow?.value?.components(separatedBy: ","))! |
|
|
|
// let tickets = ticketHolders.flatMap { $0.tickets } |
|
|
|
|
|
|
|
// let filteredTickets = tickets.filter { UInt16($0.id, radix: 16)! == id } |
|
|
|
|
|
|
|
// return filteredTickets[Int(id)] |
|
|
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func generateQRCode(from string: String) -> UIImage? { |
|
|
|
|
|
|
|
return string.toQRCode() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func ticketIdsValidated() -> Bool { |
|
|
|
private func configureBalanceViewModel() { |
|
|
|
let rowValue = ticketIdsRow?.value |
|
|
|
switch transferType { |
|
|
|
if rowValue == nil || rowValue!.isEmpty { |
|
|
|
case .ether: |
|
|
|
return false |
|
|
|
session.balanceViewModel.subscribe { viewModel in |
|
|
|
} |
|
|
|
guard let viewModel = viewModel else { return } |
|
|
|
let ticketIds = getTicketIds() |
|
|
|
let amount = viewModel.amountShort |
|
|
|
for id in ticketIds { |
|
|
|
self.headerViewModel.title = "\(amount) \(self.session.config.server.name) (\(viewModel.symbol))" |
|
|
|
guard id.isNumeric() else { |
|
|
|
if let viewModel = self.viewModel { |
|
|
|
return false |
|
|
|
self.configure(viewModel: viewModel) |
|
|
|
} |
|
|
|
} |
|
|
|
guard let intId = UInt16(id) else { |
|
|
|
|
|
|
|
return false |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
guard isTicketExisting(for: intId) else { |
|
|
|
session.refresh(.ethBalance) |
|
|
|
return false |
|
|
|
case .token(let token): |
|
|
|
|
|
|
|
let viewModel = BalanceTokenViewModel(token: token) |
|
|
|
|
|
|
|
let amount = viewModel.amountShort |
|
|
|
|
|
|
|
headerViewModel.title = "\(amount) \(viewModel.symbol)" |
|
|
|
|
|
|
|
if let viewModel = self.viewModel { |
|
|
|
|
|
|
|
configure(viewModel: self.viewModel) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func getIndiciesFromUI() -> [UInt16] { |
|
|
|
private func makeTargetAddressRightView() -> UIView { |
|
|
|
let ticketIds = getTicketIds() |
|
|
|
let pasteButton = Button(size: .normal, style: .borderless) |
|
|
|
return ticketIds.map { (getTicket(for: UInt16($0)!)?.index)! } |
|
|
|
pasteButton.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
pasteButton.setTitle(R.string.localizable.sendPasteButtonTitle(), for: .normal) |
|
|
|
|
|
|
|
pasteButton.titleLabel?.font = Fonts.regular(size: 14)! |
|
|
|
|
|
|
|
pasteButton.setTitleColor(UIColor(red: 155, green: 155, blue: 155), for: .normal) |
|
|
|
|
|
|
|
pasteButton.addTarget(self, action: #selector(pasteAction), for: .touchUpInside) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let scanQRCodeButton = Button(size: .normal, style: .borderless) |
|
|
|
|
|
|
|
scanQRCodeButton.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
scanQRCodeButton.setImage(R.image.qr_code_icon(), for: .normal) |
|
|
|
|
|
|
|
scanQRCodeButton.setTitleColor(UIColor(red: 155, green: 155, blue: 155), for: .normal) |
|
|
|
|
|
|
|
scanQRCodeButton.addTarget(self, action: #selector(openReader), for: .touchUpInside) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let targetAddressRightView = UIStackView(arrangedSubviews: [ |
|
|
|
|
|
|
|
pasteButton, |
|
|
|
|
|
|
|
scanQRCodeButton, |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
targetAddressRightView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
targetAddressRightView.distribution = .equalSpacing |
|
|
|
|
|
|
|
targetAddressRightView.spacing = 0 |
|
|
|
|
|
|
|
targetAddressRightView.axis = .horizontal |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return targetAddressRightView |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func makeAmountRightView() -> UIView { |
|
|
|
|
|
|
|
let fiatButton = Button(size: .normal, style: .borderless) |
|
|
|
|
|
|
|
fiatButton.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
fiatButton.setTitle(currentPair.left, for: .normal) |
|
|
|
|
|
|
|
fiatButton.setTitleColor(UIColor(red: 155, green: 155, blue: 155), for: .normal) |
|
|
|
|
|
|
|
fiatButton.addTarget(self, action: #selector(fiatAction), for: .touchUpInside) |
|
|
|
|
|
|
|
fiatButton.isHidden = !viewModel.showAlternativeAmount |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let amountRightView = UIStackView(arrangedSubviews: [ |
|
|
|
|
|
|
|
fiatButton, |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
amountRightView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
|
|
|
|
amountRightView.distribution = .equalSpacing |
|
|
|
|
|
|
|
amountRightView.spacing = 1 |
|
|
|
|
|
|
|
amountRightView.axis = .horizontal |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return amountRightView |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private func makeToolbarWithDoneButton() -> UIToolbar { |
|
|
|
|
|
|
|
//Frame needed, but actual values aren't that important |
|
|
|
|
|
|
|
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 40)) |
|
|
|
|
|
|
|
toolbar.barStyle = .default |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) |
|
|
|
|
|
|
|
let done = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(closeKeyboard)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
toolbar.items = [flexSpace, done] |
|
|
|
|
|
|
|
toolbar.sizeToFit() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return toolbar |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -409,8 +606,7 @@ extension SendViewController: QRCodeReaderDelegate { |
|
|
|
guard let result = QRURLParser.from(string: result) else { |
|
|
|
guard let result = QRURLParser.from(string: result) else { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
addressRow?.value = result.address |
|
|
|
targetAddressTextField.text = result.address |
|
|
|
addressRow?.reload() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let dataString = result.params["data"] { |
|
|
|
if let dataString = result.params["data"] { |
|
|
|
data = Data(hex: dataString.drop0x) |
|
|
|
data = Data(hex: dataString.drop0x) |
|
|
@ -419,50 +615,32 @@ extension SendViewController: QRCodeReaderDelegate { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if let value = result.params["amount"] { |
|
|
|
if let value = result.params["amount"] { |
|
|
|
amountRow?.value = EtherNumberFormatter.full.string(from: BigInt(value) ?? BigInt(), units: .ether) |
|
|
|
amountTextField.text = EtherNumberFormatter.full.string(from: BigInt(value) ?? BigInt(), units: .ether) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
amountRow?.value = "" |
|
|
|
amountTextField.text = "" |
|
|
|
} |
|
|
|
} |
|
|
|
amountRow?.reload() |
|
|
|
|
|
|
|
pairValue = 0.0 |
|
|
|
pairValue = 0.0 |
|
|
|
updatePriceSection() |
|
|
|
updatePriceSection() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private func valueOfPairRepresantetion() -> String { |
|
|
|
|
|
|
|
var formattedString = "" |
|
|
|
|
|
|
|
if self.currentPair.left == viewModel.symbol { |
|
|
|
|
|
|
|
formattedString = StringFormatter().currency(with: self.pairValue, and: self.session.config.currency.rawValue) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
formattedString = stringFormatter.formatter(for: self.pairValue) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return "~ \(formattedString) " + "\(currentPair.right)" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
extension SendViewController: UITextFieldDelegate { |
|
|
|
extension SendViewController: UITextFieldDelegate { |
|
|
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { |
|
|
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { |
|
|
|
guard let input = textField.text else { |
|
|
|
if textField == targetAddressTextField { |
|
|
|
|
|
|
|
return addressTextFieldChanged(in: range, to: string) |
|
|
|
|
|
|
|
} else if textField == amountTextField { |
|
|
|
|
|
|
|
return amountTextFieldChanged(in: range, to: string) |
|
|
|
|
|
|
|
} else { |
|
|
|
return true |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
//In this step we validate only allowed characters it is because of the iPad keyboard. |
|
|
|
} |
|
|
|
let characterSet = NSCharacterSet(charactersIn: self.allowedCharacters).inverted |
|
|
|
|
|
|
|
let separatedChars = string.components(separatedBy: characterSet) |
|
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool { |
|
|
|
let filteredNumbersAndSeparator = separatedChars.joined(separator: "") |
|
|
|
if textField == targetAddressTextField { |
|
|
|
if string != filteredNumbersAndSeparator { |
|
|
|
activateAmountView() |
|
|
|
return false |
|
|
|
} else if textField == amountTextField { |
|
|
|
} |
|
|
|
view.endEditing(true) |
|
|
|
//This is required to prevent user from input of numbers like 1.000.25 or 1,000,25. |
|
|
|
|
|
|
|
if string == "," || string == "." || string == "'" { |
|
|
|
|
|
|
|
return !input.contains(string) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let text = (input as NSString).replacingCharacters(in: range, with: string) |
|
|
|
|
|
|
|
guard let amount = decimalFormatter.number(from: text) else { |
|
|
|
|
|
|
|
//Should be done in another way. |
|
|
|
|
|
|
|
pairValue = 0.0 |
|
|
|
|
|
|
|
updatePriceSection() |
|
|
|
|
|
|
|
return true |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
self.updatePairPrice(with: amount.doubleValue) |
|
|
|
|
|
|
|
return true |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|