diff --git a/AlphaWallet.xcodeproj/project.pbxproj b/AlphaWallet.xcodeproj/project.pbxproj index 21be7357c..b8f6cb828 100644 --- a/AlphaWallet.xcodeproj/project.pbxproj +++ b/AlphaWallet.xcodeproj/project.pbxproj @@ -661,6 +661,8 @@ 87D163A4242CD811002662D2 /* AddHideTokensCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D163A1242CD811002662D2 /* AddHideTokensCoordinator.swift */; }; 87D163A7242CD9A2002662D2 /* AddHideTokenSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D163A5242CD9A1002662D2 /* AddHideTokenSectionHeaderView.swift */; }; 87D163A8242CD9A2002662D2 /* AddHideTokensView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D163A6242CD9A1002662D2 /* AddHideTokensView.swift */; }; + 87ED8F8F2484F0D40005C69B /* SendViewSectionHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87ED8F8E2484F0D40005C69B /* SendViewSectionHeaderViewModel.swift */; }; + 87ED8F912484F1740005C69B /* SendViewSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87ED8F902484F1740005C69B /* SendViewSectionHeader.swift */; }; AA26C61F20412A1E00318B9B /* TokensCardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA26C61D20412A1D00318B9B /* TokensCardViewController.swift */; }; AA26C62320412A4100318B9B /* UIViewInspectableEnhancements.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA26C62120412A4100318B9B /* UIViewInspectableEnhancements.swift */; }; AA26C62420412A4100318B9B /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA26C62220412A4100318B9B /* Double.swift */; }; @@ -1378,6 +1380,8 @@ 87D163A1242CD811002662D2 /* AddHideTokensCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddHideTokensCoordinator.swift; sourceTree = ""; }; 87D163A5242CD9A1002662D2 /* AddHideTokenSectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddHideTokenSectionHeaderView.swift; sourceTree = ""; }; 87D163A6242CD9A1002662D2 /* AddHideTokensView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddHideTokensView.swift; sourceTree = ""; }; + 87ED8F8E2484F0D40005C69B /* SendViewSectionHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewSectionHeaderViewModel.swift; sourceTree = ""; }; + 87ED8F902484F1740005C69B /* SendViewSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewSectionHeader.swift; sourceTree = ""; }; AA26C61D20412A1D00318B9B /* TokensCardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCardViewController.swift; sourceTree = ""; }; AA26C62120412A4100318B9B /* UIViewInspectableEnhancements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewInspectableEnhancements.swift; sourceTree = ""; }; AA26C62220412A4100318B9B /* Double.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; @@ -2005,6 +2009,7 @@ 5E7C73BD3FD2C2844F5DEA45 /* SegmentedControl.swift */, 5E7C78FF8A5682C27E15B488 /* UITableViewCell+TokenCell.swift */, 5E7C709FA1317A56BC12CD35 /* AddHideTokensCell.swift */, + 87ED8F902484F1740005C69B /* SendViewSectionHeader.swift */, ); path = Views; sourceTree = ""; @@ -2053,6 +2058,7 @@ 5E7C799AF966DBF0D59DFB20 /* SegmentedControlViewModel.swift */, 8782035E2431FBC300792F12 /* ShowAddHideTokensViewModel.swift */, 87C8018B24350174007648CF /* AddHideTokenSectionHeaderViewModel.swift */, + 87ED8F8E2484F0D40005C69B /* SendViewSectionHeaderViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -4194,6 +4200,7 @@ 5E7C70AE62DBB193399C7F5E /* ServerViewCell.swift in Sources */, 5E7C7EB845B0EE96CC8DCF43 /* ServerViewModel.swift in Sources */, 5E7C745A423BD10CFDED9A81 /* ServersViewModel.swift in Sources */, + 87ED8F912484F1740005C69B /* SendViewSectionHeader.swift in Sources */, 5E7C7ECE164289A89734B4EF /* LocalesCoordinator.swift in Sources */, 5E7C730C6AEF556AFB9A4B2C /* LocalesViewController.swift in Sources */, 5E7C718043636901114BF76C /* LocalesViewModel.swift in Sources */, @@ -4376,6 +4383,7 @@ 5E7C79DE18FC5CB46E75753A /* AssetAttributeMapping.swift in Sources */, 5E7C76DD6335158C70AA4F12 /* TokenScriptSignatureVerifier.swift in Sources */, 5E7C77AF8CA540D8F1404B6F /* AssetAttribute.swift in Sources */, + 87ED8F8F2484F0D40005C69B /* SendViewSectionHeaderViewModel.swift in Sources */, 5E7C733D2CA2A0FC585D93D1 /* AssetInternalValue.swift in Sources */, 5E7C70794E07FF6B26AE297B /* DirectoryContentsWatcher.swift in Sources */, 5E7C757F9E9F7738B213C8B8 /* AssetDefinitionDiskBackingStore.swift in Sources */, diff --git a/AlphaWallet/Localization/en.lproj/Localizable.strings b/AlphaWallet/Localization/en.lproj/Localizable.strings index 3ef254173..7a6938769 100644 --- a/AlphaWallet/Localization/en.lproj/Localizable.strings +++ b/AlphaWallet/Localization/en.lproj/Localizable.strings @@ -62,6 +62,9 @@ "send.error.wrongInput" = "Wrong Input"; "send.paste.button.title" = "Paste"; "send.paste.button.addressBook" = "Address Book"; +"send.amount" = "Amount"; +"send.recipient" = "Recipient"; +"send.recipientsAddress" = "Recipient’s Address"; "settings.biometricsDisabled.label.title" = "Passcode"; "settings.biometricsEnabled.label.title" = "Passcode / %@"; "settings.error.failedToSendEmail" = "Failed to send email. Make sure you have Mail app installed."; diff --git a/AlphaWallet/Localization/es.lproj/Localizable.strings b/AlphaWallet/Localization/es.lproj/Localizable.strings index a5a620978..e52a1f68a 100644 --- a/AlphaWallet/Localization/es.lproj/Localizable.strings +++ b/AlphaWallet/Localization/es.lproj/Localizable.strings @@ -62,6 +62,9 @@ "send.error.wrongInput" = "Entrada incorrecta"; "send.paste.button.title" = "Pegar"; "send.paste.button.addressBook" = "Address Book"; +"send.amount" = "Amount"; +"send.recipient" = "Recipient"; +"send.recipientsAddress" = "Recipient’s Address"; "settings.biometricsDisabled.label.title" = "Código de acceso"; "settings.biometricsEnabled.label.title" = "Código de acceso / %@"; "settings.error.failedToSendEmail" = "Error al enviar el correo electrónico. Asegúrate de que tienes una aplicación de correo instalada."; diff --git a/AlphaWallet/Localization/ja.lproj/Localizable.strings b/AlphaWallet/Localization/ja.lproj/Localizable.strings index 6365e3175..4c3e1c6a1 100644 --- a/AlphaWallet/Localization/ja.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ja.lproj/Localizable.strings @@ -62,6 +62,9 @@ "send.error.wrongInput" = "誤った入力"; "send.paste.button.title" = "ペースト"; "send.paste.button.addressBook" = "Address Book"; +"send.amount" = "Amount"; +"send.recipient" = "Recipient"; +"send.recipientsAddress" = "Recipient’s Address"; "settings.biometricsDisabled.label.title" = "パスコード"; "settings.biometricsEnabled.label.title" = "パスコード/ %@"; "settings.error.failedToSendEmail" = "メールを送信できませんでした。メール アプリのインストールを確認してください。"; diff --git a/AlphaWallet/Localization/ko.lproj/Localizable.strings b/AlphaWallet/Localization/ko.lproj/Localizable.strings index 4cb263fcc..dc52d229e 100644 --- a/AlphaWallet/Localization/ko.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ko.lproj/Localizable.strings @@ -62,6 +62,9 @@ "send.error.wrongInput" = "잘못된 입력"; "send.paste.button.title" = "붙여넣기"; "send.paste.button.addressBook" = "Address Book"; +"send.amount" = "Amount"; +"send.recipient" = "Recipient"; +"send.recipientsAddress" = "Recipient’s Address"; "settings.biometricsDisabled.label.title" = "암호"; "settings.biometricsEnabled.label.title" = "암호 / %@"; "settings.error.failedToSendEmail" = "이메일을 전송하지 못했습니다. 메일 앱이 설치되어 있는지 확인하십시오."; diff --git a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings index 59beadf18..86db09665 100644 --- a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings +++ b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings @@ -54,6 +54,9 @@ "send.error.wrongInput" = "错误的输入"; "send.paste.button.title" = "粘贴"; "send.paste.button.addressBook" = "Address Book"; +"send.amount" = "Amount"; +"send.recipient" = "Recipient"; +"send.recipientsAddress" = "Recipient’s Address"; "settings.biometricsDisabled.label.title" = "密码"; "settings.biometricsEnabled.label.title" = "密码 / %@"; "settings.error.failedToSendEmail" = "无法发送邮件。请确保你已经安装了邮件应用。"; diff --git a/AlphaWallet/Tokens/ViewModels/SendViewSectionHeaderViewModel.swift b/AlphaWallet/Tokens/ViewModels/SendViewSectionHeaderViewModel.swift new file mode 100644 index 000000000..0e07172d8 --- /dev/null +++ b/AlphaWallet/Tokens/ViewModels/SendViewSectionHeaderViewModel.swift @@ -0,0 +1,29 @@ +// +// SendViewSectionHeaderViewModel.swift +// AlphaWallet +// +// Created by Vladyslav Shepitko on 01.06.2020. +// + +import UIKit + +struct SendViewSectionHeaderViewModel { + + let text: String + var showTopSeparatorLine: Bool = true + + var font: UIFont { + return Fonts.semibold(size: 15)! + } + var textColor: UIColor { + return R.color.dove()! + } + var backgroundColor: UIColor { + return R.color.alabaster()! + } + + var separatorBackgroundColor: UIColor { + return R.color.mike()! + } +} + diff --git a/AlphaWallet/Tokens/Views/SendViewSectionHeader.swift b/AlphaWallet/Tokens/Views/SendViewSectionHeader.swift new file mode 100644 index 000000000..729d027f8 --- /dev/null +++ b/AlphaWallet/Tokens/Views/SendViewSectionHeader.swift @@ -0,0 +1,77 @@ +// +// SendViewSectionHeader.swift +// AlphaWallet +// +// Created by Vladyslav Shepitko on 01.06.2020. +// + +import UIKit + +class SendViewSectionHeader: UIView { + + private let textLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 0 + label.setContentHuggingPriority(.required, for: .vertical) + label.setContentCompressionResistancePriority(.required, for: .vertical) + + return label + }() + + private let topSeparatorView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + + private let bottomSeparatorView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + private var topSeparatorLineHeight: NSLayoutConstraint! + + init() { + super.init(frame: .zero) + setupView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupView() { + translatesAutoresizingMaskIntoConstraints = false + + addSubview(topSeparatorView) + addSubview(textLabel) + addSubview(bottomSeparatorView) + + NSLayoutConstraint.activate([ + textLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16), + textLabel.trailingAnchor.constraint(greaterThanOrEqualTo: trailingAnchor, constant: -16), + textLabel.topAnchor.constraint(equalTo: topAnchor, constant: 13), + textLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -13), + + topSeparatorView.topAnchor.constraint(equalTo: topAnchor), + topSeparatorView.widthAnchor.constraint(equalTo: widthAnchor), + + bottomSeparatorView.topAnchor.constraint(equalTo: bottomAnchor), + bottomSeparatorView.widthAnchor.constraint(equalTo: widthAnchor), + bottomSeparatorView.heightAnchor.constraint(equalToConstant: 1) + ]) + topSeparatorLineHeight = topSeparatorView.heightAnchor.constraint(equalToConstant: 1) + topSeparatorLineHeight.isActive = true + } + + func configure(viewModel: SendViewSectionHeaderViewModel) { + textLabel.text = viewModel.text + textLabel.textColor = viewModel.textColor + textLabel.font = viewModel.font + backgroundColor = viewModel.backgroundColor + topSeparatorView.backgroundColor = viewModel.separatorBackgroundColor + bottomSeparatorView.backgroundColor = viewModel.separatorBackgroundColor + topSeparatorLineHeight.constant = viewModel.showTopSeparatorLine ? 1 : 0 + } +} diff --git a/AlphaWallet/Transfer/ViewControllers/SendViewController.swift b/AlphaWallet/Transfer/ViewControllers/SendViewController.swift index 8c3227c56..34a1a9423 100644 --- a/AlphaWallet/Transfer/ViewControllers/SendViewController.swift +++ b/AlphaWallet/Transfer/ViewControllers/SendViewController.swift @@ -22,12 +22,12 @@ protocol SendViewControllerDelegate: class, CanOpenURL { class SendViewController: UIViewController, CanScanQRCode { private let roundedBackground = RoundedBackground() private let scrollView = UIScrollView() - private let header = SendHeaderViewWithIntroduction() - private let targetAddressLabel = UILabel() + private let recipientHeader = SendViewSectionHeader() + private let amountHeader = SendViewSectionHeader() + private let recepientAddressLabel = UILabel() private let amountLabel = UILabel() private let buttonsBar = ButtonsBar(numberOfButtons: 1) private var viewModel: SendViewModel - lazy private var headerViewModel = SendHeaderViewViewModelWithIntroduction(server: session.server, assetDefinitionStore: assetDefinitionStore) private var balanceViewModel: BalanceBaseViewModel? private let session: WalletSession private let account: EthereumAccount @@ -40,6 +40,13 @@ class SendViewController: UIViewController, CanScanQRCode { }() private var currentSubscribableKeyForNativeCryptoCurrencyBalance: Subscribable.SubscribableKey? private var currentSubscribableKeyForNativeCryptoCurrencyPrice: Subscribable.SubscribableKey? + private let amountViewModel = SendViewSectionHeaderViewModel( + text: R.string.localizable.sendAmount().uppercased(), + showTopSeparatorLine: false + ) + private let recipientViewModel = SendViewSectionHeaderViewModel( + text: R.string.localizable.sendRecipient().uppercased() + ) let targetAddressTextField = AddressTextField() lazy var amountTextField = AmountTextField(server: session.server) weak var delegate: SendViewControllerDelegate? @@ -84,10 +91,11 @@ class SendViewController: UIViewController, CanScanQRCode { addressControlsContainer.translatesAutoresizingMaskIntoConstraints = false addressControlsContainer.backgroundColor = .clear + targetAddressTextField.pasteButton.contentHorizontalAlignment = .right let addressControlsStackView = [ targetAddressTextField.pasteButton, targetAddressTextField.clearButton - ].asStackView(axis: .horizontal) + ].asStackView(axis: .horizontal, alignment: .trailing) addressControlsStackView.translatesAutoresizingMaskIntoConstraints = false addressControlsStackView.setContentHuggingPriority(.required, for: .horizontal) addressControlsStackView.setContentCompressionResistancePriority(.required, for: .horizontal) @@ -95,19 +103,21 @@ class SendViewController: UIViewController, CanScanQRCode { addressControlsContainer.addSubview(addressControlsStackView) let stackView = [ - header, - .spacer(height: ScreenChecker().isNarrowScreen ? 7 : 14), + amountHeader, + .spacer(height: ScreenChecker().isNarrowScreen ? 7 : 27), amountLabel, .spacer(height: ScreenChecker().isNarrowScreen ? 2 : 4), amountTextField, .spacer(height: 4), - amountTextField.alternativeAmountLabel, - .spacer(height: ScreenChecker().isNarrowScreen ? 7: 20), - targetAddressLabel, + [.spacerWidth(16), amountTextField.alternativeAmountLabel].asStackView(axis: .horizontal), + .spacer(height: ScreenChecker().isNarrowScreen ? 7: 14), + recipientHeader, + .spacer(height: ScreenChecker().isNarrowScreen ? 7: 16), + [.spacerWidth(16), recepientAddressLabel].asStackView(axis: .horizontal), .spacer(height: ScreenChecker().isNarrowScreen ? 2 : 4), targetAddressTextField, .spacer(height: 4), [ - [targetAddressTextField.ensAddressLabel, targetAddressTextField.statusLabel].asStackView(axis: .horizontal, alignment: .leading), + [.spacerWidth(16), targetAddressTextField.ensAddressLabel, targetAddressTextField.statusLabel].asStackView(axis: .horizontal, alignment: .leading), addressControlsContainer ].asStackView(axis: .horizontal), ].asStackView(axis: .vertical) @@ -122,14 +132,20 @@ class SendViewController: UIViewController, CanScanQRCode { footerBar.addSubview(buttonsBar) NSLayoutConstraint.activate([ - header.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30), - header.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30), + amountHeader.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 0), + amountHeader.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: 0), + amountHeader.heightAnchor.constraint(equalToConstant: 50), - targetAddressTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30), - targetAddressTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30), + recipientHeader.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 0), + recipientHeader.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: 0), + recipientHeader.heightAnchor.constraint(equalToConstant: 50), - amountTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 30), - amountTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -30), + recepientAddressLabel.heightAnchor.constraint(equalToConstant: 22), + targetAddressTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 16), + targetAddressTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -16), + + amountTextField.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor, constant: 16), + amountTextField.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor, constant: -16), amountTextField.heightAnchor.constraint(equalToConstant: ScreenChecker().isNarrowScreen ? 30 : 50), stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor), @@ -152,7 +168,7 @@ class SendViewController: UIViewController, CanScanQRCode { scrollView.topAnchor.constraint(equalTo: view.topAnchor), scrollView.bottomAnchor.constraint(equalTo: footerBar.topAnchor), - addressControlsStackView.trailingAnchor.constraint(equalTo: addressControlsContainer.trailingAnchor), + addressControlsStackView.trailingAnchor.constraint(equalTo: addressControlsContainer.trailingAnchor, constant: -7), addressControlsStackView.topAnchor.constraint(equalTo: addressControlsContainer.topAnchor), addressControlsStackView.bottomAnchor.constraint(equalTo: addressControlsContainer.bottomAnchor), addressControlsStackView.leadingAnchor.constraint(greaterThanOrEqualTo: addressControlsContainer.leadingAnchor), @@ -164,6 +180,11 @@ class SendViewController: UIViewController, CanScanQRCode { getGasPrice() } + override func viewDidLoad() { + super.viewDidLoad() + activateAmountView() + } + @objc func closeKeyboard() { view.endEditing(true) } @@ -179,17 +200,19 @@ class SendViewController: UIViewController, CanScanQRCode { view.backgroundColor = viewModel.backgroundColor - headerViewModel.showAlternativeAmount = viewModel.showAlternativeAmount - header.configure(viewModel: headerViewModel) + amountHeader.configure(viewModel: amountViewModel) + recipientHeader.configure(viewModel: recipientViewModel) - targetAddressLabel.font = viewModel.textFieldsLabelFont - targetAddressLabel.textColor = viewModel.textFieldsLabelTextColor + recepientAddressLabel.text = viewModel.recipientsAddress + recepientAddressLabel.font = viewModel.recepientLabelFont + recepientAddressLabel.textColor = viewModel.recepientLabelTextColor amountLabel.font = viewModel.textFieldsLabelFont amountLabel.textColor = viewModel.textFieldsLabelTextColor switch transferType { case .nativeCryptocurrency(_, let recipient, let amount): + amountTextField.selectCurrencyButton.isHidden = false if let recipient = recipient { targetAddressTextField.value = recipient.stringValue targetAddressTextField.queueEnsResolution(ofValue: recipient.stringValue) @@ -202,7 +225,9 @@ class SendViewController: UIViewController, CanScanQRCode { self?.amountTextField.cryptoToDollarRate = value } } + amountTextField.isAlternativeAmountEnabled = true case .ERC20Token(_, let recipient, let amount): + amountTextField.selectCurrencyButton.isHidden = true if let recipient = recipient { targetAddressTextField.value = recipient.stringValue targetAddressTextField.queueEnsResolution(ofValue: recipient.stringValue) @@ -213,6 +238,7 @@ class SendViewController: UIViewController, CanScanQRCode { amountTextField.isAlternativeAmountEnabled = false amountTextField.isFiatButtonHidden = true case .ERC875Token, .ERC875TokenOrder, .ERC721Token, .ERC721ForTicketToken, .dapp: + amountTextField.selectCurrencyButton.isHidden = true amountTextField.isAlternativeAmountEnabled = false amountTextField.isFiatButtonHidden = true } @@ -221,6 +247,12 @@ class SendViewController: UIViewController, CanScanQRCode { let nextButton = buttonsBar.buttons[0] nextButton.setTitle(R.string.localizable.send(), for: .normal) nextButton.addTarget(self, action: #selector(send), for: .touchUpInside) + + updateNavigationTitle() + } + + private func updateNavigationTitle() { + title = "\(R.string.localizable.send()) \(transferType.symbol)" } func getGasPrice() { @@ -236,10 +268,10 @@ class SendViewController: UIViewController, CanScanQRCode { @objc func send() { let input = targetAddressTextField.value.trimmed - self.targetAddressTextField.errorState = .none + targetAddressTextField.errorState = .none guard let address = AlphaWallet.Address(string: input) else { - self.targetAddressTextField.errorState = .error(Errors.invalidAddress.prettyError) + targetAddressTextField.errorState = .error(Errors.invalidAddress.prettyError) return } @@ -309,47 +341,23 @@ class SendViewController: UIViewController, CanScanQRCode { currentSubscribableKeyForNativeCryptoCurrencyBalance.flatMap { session.balanceViewModel.unsubscribe($0) } currentSubscribableKeyForNativeCryptoCurrencyPrice.flatMap { ethPrice.unsubscribe($0) } switch transferType { - case .nativeCryptocurrency: + case .nativeCryptocurrency(_, let recipient, let amount): currentSubscribableKeyForNativeCryptoCurrencyBalance = session.balanceViewModel.subscribe { [weak self] viewModel in - guard let celf = self, let viewModel = viewModel else { return } - let amount = viewModel.amountShort - celf.headerViewModel.title = "\(amount) \(celf.session.server.name) (\(viewModel.symbol))" - let etherToken = TokensDataStore.etherToken(forServer: celf.session.server) - let ticker = celf.storage.coinTicker(for: etherToken) - celf.headerViewModel.ticker = ticker - celf.headerViewModel.currencyAmount = celf.session.balanceCoordinator.viewModel.currencyAmount - celf.headerViewModel.currencyAmountWithoutSymbol = celf.session.balanceCoordinator.viewModel.currencyAmountWithoutSymbol + guard let celf = self else { return } guard let tokenObject = celf.storage.token(forContract: celf.viewModel.transferType.contract) else { return } - //TODO handle if no ens/address? Seems no need to worry for now - guard let ensOrAddress = AddressOrEnsName(string: celf.targetAddressTextField.value) else { return } - let amountAsIntWithDecimals = EtherNumberFormatter.full.number(from: celf.amountTextField.ethCost, decimals: tokenObject.decimals) - celf.configureFor(contract: celf.viewModel.transferType.contract, recipient: ensOrAddress, amount: amountAsIntWithDecimals, shouldConfigureBalance: false) + celf.configureFor(contract: celf.viewModel.transferType.contract, recipient: recipient, amount: amount, shouldConfigureBalance: false) } session.refresh(.ethBalance) - case .ERC20Token(let token, _, _): - let viewModel = BalanceTokenViewModel(token: token) - let amount = viewModel.amountShort - //Note that if we want to display the token name directly from token.name, we have to be careful that DAI token's name has trailing \0 - headerViewModel.title = "\(amount) \(token.titleInPluralForm(withAssetDefinitionStore: assetDefinitionStore))" - let etherToken = TokensDataStore.etherToken(forServer: session.server) - let ticker = storage.coinTicker(for: etherToken) - headerViewModel.ticker = ticker - headerViewModel.currencyAmount = session.balanceCoordinator.viewModel.currencyAmount - headerViewModel.currencyAmountWithoutSymbol = session.balanceCoordinator.viewModel.currencyAmountWithoutSymbol - - //TODO is this the best place to put it? because this func is called configureBalanceViewModel() "balance" - headerViewModel.contractAddress = token.contractAddress - - let amountAsIntWithDecimals = EtherNumberFormatter.full.number(from: amountTextField.ethCost, decimals: token.decimals) - guard let ensOrAddress = AddressOrEnsName(string: targetAddressTextField.value) else { return } - configureFor(contract: self.viewModel.transferType.contract, recipient: ensOrAddress, amount: amountAsIntWithDecimals, shouldConfigureBalance: false) + case .ERC20Token(let token, let recipient, let amount): + let amount = amount.flatMap { EtherNumberFormatter.full.number(from: $0, decimals: token.decimals) } + configureFor(contract: viewModel.transferType.contract, recipient: recipient, amount: amount, shouldConfigureBalance: false) case .ERC875Token, .ERC875TokenOrder, .ERC721Token, .ERC721ForTicketToken, .dapp: break } } func didScanQRCode(_ result: String) { - self.activateAmountView() + activateAmountView() guard let result = QRCodeValueParser.from(string: result) else { return } switch result { @@ -441,7 +449,7 @@ extension SendViewController: AmountTextFieldDelegate { } func changeType(in textField: AmountTextField) { - //do nothing + updateNavigationTitle() } } @@ -460,7 +468,7 @@ extension SendViewController: AddressTextFieldDelegate { } func shouldReturn(in textField: AddressTextField) -> Bool { - activateAmountView() + textField.resignFirstResponder() return true } diff --git a/AlphaWallet/Transfer/ViewModels/SendViewModel.swift b/AlphaWallet/Transfer/ViewModels/SendViewModel.swift index 185416497..c0c0344cd 100644 --- a/AlphaWallet/Transfer/ViewModels/SendViewModel.swift +++ b/AlphaWallet/Transfer/ViewModels/SendViewModel.swift @@ -55,4 +55,16 @@ struct SendViewModel { var textFieldsLabelFont: UIFont { return Fonts.regular(size: 10)! } + + var recepientLabelFont: UIFont { + return Fonts.regular(size: 13)! + } + + var recepientLabelTextColor: UIColor { + return R.color.dove()! + } + + var recipientsAddress: String { + return R.string.localizable.sendRecipientsAddress() + } } diff --git a/AlphaWallet/UI/AddressTextField.swift b/AlphaWallet/UI/AddressTextField.swift index 827df7f98..0e3d17688 100644 --- a/AlphaWallet/UI/AddressTextField.swift +++ b/AlphaWallet/UI/AddressTextField.swift @@ -24,10 +24,10 @@ class AddressTextField: UIControl { button.setTitleColor(DataEntry.Color.icon, for: .normal) button.backgroundColor = .clear button.contentHorizontalAlignment = .right - + return button }() - + var clearButton: Button = { let button = Button(size: .normal, style: .borderless) button.translatesAutoresizingMaskIntoConstraints = false @@ -36,10 +36,10 @@ class AddressTextField: UIControl { button.setTitleColor(DataEntry.Color.icon, for: .normal) button.backgroundColor = .clear button.contentHorizontalAlignment = .right - + return button }() - + let label = UILabel() let ensAddressLabel: UILabel = { let label = UILabel() @@ -47,20 +47,20 @@ class AddressTextField: UIControl { label.numberOfLines = 0 label.setContentHuggingPriority(.required, for: .horizontal) label.setContentCompressionResistancePriority(.required, for: .horizontal) - + return label }() - + let statusLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.numberOfLines = 0 label.setContentHuggingPriority(.required, for: .horizontal) label.setContentCompressionResistancePriority(.required, for: .horizontal) - + return label }() - + var value: String { get { if let ensResolvedAddress = ensAddressLabel.text, !ensResolvedAddress.isEmpty { @@ -73,10 +73,10 @@ class AddressTextField: UIControl { //Client code sometimes sets back the address. We only set (and thus clear the ENS name) if it doesn't match the resolved address guard ensAddressLabel.text != newValue else { return } textField.text = newValue - + let notification = Notification(name: UITextField.textDidChangeNotification, object: textField) NotificationCenter.default.post(notification) - + clearAddressFromResolvingEnsName() } } @@ -89,7 +89,7 @@ class AddressTextField: UIControl { textField.returnKeyType = newValue } } - + var errorState: TextField.TextFieldErrorState = .none { didSet { switch errorState { @@ -100,14 +100,14 @@ class AddressTextField: UIControl { self.ensAddressLabel.isHidden = true case .none: statusLabel.text = nil - statusLabel.isHidden = true + statusLabel.isHidden = true } - + let borderColor = errorState.textFieldBorderColor(whileEditing: isFirstResponder) let shouldDropShadow = errorState.textFieldShowShadow(whileEditing: isFirstResponder) - + layer.borderColor = borderColor.cgColor - + dropShadow(color: shouldDropShadow ? borderColor : .clear, radius: DataEntry.Metric.shadowRadius) } } @@ -118,7 +118,7 @@ class AddressTextField: UIControl { super.init(frame: .zero) pasteButton.addTarget(self, action: #selector(pasteAction), for: .touchUpInside) clearButton.addTarget(self, action: #selector(clearAction), for: .touchUpInside) - + textField.translatesAutoresizingMaskIntoConstraints = false textField.delegate = self textField.leftViewMode = .always @@ -137,25 +137,25 @@ class AddressTextField: UIControl { ensAddressLabel.font = DataEntry.Font.label ensAddressLabel.textAlignment = .center updateClearAndPasteButtons(textField.text ?? "") - + NSLayoutConstraint.activate([ textField.anchorsConstraint(to: self), heightAnchor.constraint(equalToConstant: ScreenChecker().isNarrowScreen ? 30 : 50), ]) - + NotificationCenter.default.addObserver(self, selector: #selector(textDidChangeNotification(_:)), name: UITextField.textDidChangeNotification, object: nil) } - + @objc private func textDidChangeNotification(_ notification: Notification) { guard textField == notification.object as? UITextField, let text = textField.text else { return } - + updateClearAndPasteButtons(text) } - + private func updateClearAndPasteButtons(_ text: String) { clearButton.isHidden = text.isEmpty pasteButton.isHidden = !text.isEmpty @@ -175,11 +175,11 @@ class AddressTextField: UIControl { isConfigured = true cornerRadius = DataEntry.Metric.cornerRadius - + label.font = DataEntry.Font.textFieldTitle label.textColor = DataEntry.Color.label label.textAlignment = .left - + ensAddressLabel.font = DataEntry.Font.label ensAddressLabel.textColor = DataEntry.Color.ensText ensAddressLabel.isHidden = true @@ -193,27 +193,27 @@ class AddressTextField: UIControl { textField.placeholder = R.string.localizable.addressEnsLabelMessage() textField.autocapitalizationType = .none textField.autocorrectionType = .no - + statusLabel.font = DataEntry.Font.textFieldStatus statusLabel.textColor = DataEntry.Color.textFieldStatus statusLabel.textAlignment = .left - + textField.textColor = DataEntry.Color.text textField.font = DataEntry.Font.textField - + layer.borderWidth = DataEntry.Metric.borderThickness backgroundColor = DataEntry.Color.textFieldBackground layer.borderColor = errorState.textFieldBorderColor(whileEditing: isFirstResponder).cgColor errorState = .none } - + private func makeTargetAddressRightView() -> UIView { let scanQRCodeButton = Button(size: .normal, style: .borderless) scanQRCodeButton.translatesAutoresizingMaskIntoConstraints = false scanQRCodeButton.setImage(R.image.qr_code_icon(), for: .normal) scanQRCodeButton.addTarget(self, action: #selector(openReader), for: .touchUpInside) scanQRCodeButton.backgroundColor = .clear - + let targetAddressRightView = [scanQRCodeButton].asStackView(distribution: .fill) //As of iOS 13, we need to constrain the width of `rightView` let rightViewFittingSize = targetAddressRightView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) @@ -224,23 +224,23 @@ class AddressTextField: UIControl { return targetAddressRightView } - + @objc func clearAction() { textField.text?.removeAll() clearAddressFromResolvingEnsName() - + let notification = Notification(name: UITextField.textDidChangeNotification, object: textField) NotificationCenter.default.post(notification) } - + @objc func pasteAction() { clearAddressFromResolvingEnsName() - + guard let value = UIPasteboard.general.string?.trimmed else { delegate?.displayError(error: SendInputErrors.emptyClipBoard, for: self) return } - + if CryptoAddressValidator.isValidAddress(value) { self.value = value delegate?.didPaste(in: self) @@ -252,23 +252,23 @@ class AddressTextField: UIControl { textField.text = value let notification = Notification(name: UITextField.textDidChangeNotification, object: textField) NotificationCenter.default.post(notification) - + GetENSAddressCoordinator(server: serverToResolveEns).getENSAddressFromResolver(for: value) { result in guard let address = result.value else { //Don't show an error when pasting what seems like a wrong ENS name for better usability self.delegate?.didPaste(in: self) return } - + guard CryptoAddressValidator.isValidAddress(address.address) else { self.delegate?.displayError(error: Errors.invalidAddress, for: self) return } - + self.errorState = .none self.ensAddressLabel.isHidden = false self.ensAddressLabel.text = address.address - + self.delegate?.didPaste(in: self) } } @@ -280,7 +280,7 @@ class AddressTextField: UIControl { func queueEnsResolution(ofValue value: String) { errorState = .none - + let value = value.trimmed guard value.isPossibleEnsName else { return } let oldTextValue = textField.text?.trimmed @@ -291,7 +291,7 @@ class AddressTextField: UIControl { //TODO good to show an error message in the UI/label that it is not a valid ENS name return } - + guard oldTextValue == strongSelf.textField.text?.trimmed else { return } strongSelf.ensAddressLabel.isHidden = false strongSelf.ensAddressLabel.text = address.address @@ -303,27 +303,32 @@ class AddressTextField: UIControl { ensAddressLabel.text = nil ensAddressLabel.isHidden = true } + + override func resignFirstResponder() -> Bool { + super.resignFirstResponder() + return textField.resignFirstResponder() + } } extension AddressTextField: UITextFieldDelegate { - + func textFieldDidEndEditing(_ textField: UITextField) { let borderColor = errorState.textFieldBorderColor(whileEditing: false) let shouldDropShadow = errorState.textFieldShowShadow(whileEditing: false) layer.borderColor = borderColor.cgColor backgroundColor = DataEntry.Color.textFieldBackground - + dropShadow(color: shouldDropShadow ? borderColor : .clear, radius: DataEntry.Metric.shadowRadius) } - + func textFieldDidBeginEditing(_ textField: UITextField) { let borderColor = errorState.textFieldBorderColor(whileEditing: true) layer.borderColor = borderColor.cgColor backgroundColor = Colors.appWhite - + dropShadow(color: borderColor, radius: DataEntry.Metric.shadowRadius) } - + func textFieldShouldReturn(_ textField: UITextField) -> Bool { guard let delegate = delegate else { return true } return delegate.shouldReturn(in: self) diff --git a/AlphaWallet/UI/Views/AmountTextField.swift b/AlphaWallet/UI/Views/AmountTextField.swift index cfeea4db8..99fdd9be9 100644 --- a/AlphaWallet/UI/Views/AmountTextField.swift +++ b/AlphaWallet/UI/Views/AmountTextField.swift @@ -11,7 +11,7 @@ class AmountTextField: UIControl { enum Currency { case cryptoCurrency(String, UIImage) case usd(String) - + var icon: UIImage { switch self { case .cryptoCurrency(_, let image): @@ -37,6 +37,7 @@ class AmountTextField: UIControl { .font: DataEntry.Font.amountTextField!, .foregroundColor: DataEntry.Color.placeholder ]) textField.translatesAutoresizingMaskIntoConstraints = false + textField.adjustsFontSizeToFitWidth = true textField.delegate = self textField.keyboardType = .decimalPad textField.leftViewMode = .always @@ -45,23 +46,22 @@ class AmountTextField: UIControl { textField.textColor = .black textField.font = DataEntry.Font.amountTextField textField.textAlignment = .right - + return textField }() - + let statusLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false - + return label }() - + var cryptoToDollarRate: Double? = nil { didSet { if let _ = cryptoToDollarRate { updateAlternatePricingDisplay() } - isAlternativeAmountEnabled = cryptoToDollarRate != nil } } var ethCost: String { @@ -76,12 +76,14 @@ class AmountTextField: UIControl { set { switch currentPair.left { case .cryptoCurrency: - break + textField.text = newValue case .usd: - currentPair = currentPair.swapPair() - updateFiatButtonTitle() + if let amount = Double(newValue), let cryptoToDollarRate = cryptoToDollarRate { + textField.text = String(amount * cryptoToDollarRate) + } else { + textField.text = "" + } } - textField.text = newValue updateAlternatePricingDisplay() } } @@ -112,33 +114,33 @@ class AmountTextField: UIControl { label.numberOfLines = 0 label.textColor = Colors.appGreenContrastBackground label.font = DataEntry.Font.label - + return label }() - + lazy var selectCurrencyButton: SelectCurrencyButton = { let button = SelectCurrencyButton() - - switch self.currentPair.left { + + switch currentPair.left { case .cryptoCurrency(let symbol, _), .usd(let symbol): button.text = symbol - button.image = self.currentPair.left.icon + button.image = currentPair.left.icon } - + button.addTarget(self, action: #selector(fiatAction), for: .touchUpInside) - + return button }() - + private var allowedCharacters: String = { let decimalSeparator = Locale.current.decimalSeparator ?? "" return "0123456789" + decimalSeparator + EtherNumberFormatter.decimalPoint }() - + lazy var decimalFormatter: DecimalFormatter = { return DecimalFormatter() }() - + var isAlternativeAmountEnabled: Bool { get { return !alternativeAmountLabel.isHidden @@ -147,9 +149,16 @@ class AmountTextField: UIControl { alternativeAmountLabel.isHidden = !newValue } } - + + var currencySymbol: String { + switch currentPair.left { + case .cryptoCurrency(let symbol, _), .usd(let symbol): + return symbol + } + } + weak var delegate: AmountTextFieldDelegate? - + init(server: RPCServer) { switch server { case .xDai: @@ -161,11 +170,11 @@ class AmountTextField: UIControl { super.init(frame: .zero) translatesAutoresizingMaskIntoConstraints = false - + let stackView = [selectCurrencyButton, .spacerWidth(4), textField].asStackView(axis: .horizontal) stackView.translatesAutoresizingMaskIntoConstraints = false addSubview(stackView) - + computeAlternateAmount() isAlternativeAmountEnabled = cryptoToDollarRate != nil NSLayoutConstraint.activate([ @@ -175,7 +184,7 @@ class AmountTextField: UIControl { @objc func fiatAction(button: UIButton) { guard cryptoToDollarRate != nil else { return } - + let oldAlternateAmount = convertToAlternateAmount() currentPair = currentPair.swapPair() updateFiatButtonTitle() @@ -290,7 +299,7 @@ class AmountTextField: UIControl { } extension AmountTextField: UITextFieldDelegate { - + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let allowChange = amountChanged(in: range, to: string) if allowChange {