Merge branch 'master' into fix-tab-icon-colors-for-ios10

pull/163/head
James Sangalli 7 years ago committed by GitHub
commit bfae956ef9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      Trust.xcodeproj/project.pbxproj
  2. 45
      Trust/InCoordinator.swift
  3. 4
      Trust/Localization/en.lproj/Localizable.strings
  4. 4
      Trust/Transactions/Coordinators/TransactionCoordinator.swift
  5. 16
      Trust/Transactions/ViewControllers/TransactionsViewController.swift
  6. 13
      Trust/Transactions/Views/TransactionsFooterView.swift
  7. 9
      Trust/Transfer/Coordinators/PaymentCoordinator.swift
  8. 24
      Trust/Transfer/Coordinators/SendCoordinator.swift
  9. 631
      Trust/Transfer/ViewControllers/SendViewController.swift
  10. 89
      Trust/Transfer/ViewModels/SendHeaderViewViewModel.swift
  11. 135
      Trust/Transfer/ViewModels/SendViewModel.swift
  12. 132
      Trust/Transfer/Views/SendHeaderView.swift
  13. 2
      TrustTests/Coordinators/SendCoordinatorTests.swift

@ -49,7 +49,6 @@
2923D9B51FDA4E07000CF3F8 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2923D9B41FDA4E07000CF3F8 /* PasswordGenerator.swift */; };
2923D9B71FDA5E51000CF3F8 /* PasswordGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2923D9B61FDA5E51000CF3F8 /* PasswordGeneratorTests.swift */; };
29282B531F7630970067F88D /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29282B521F7630970067F88D /* Token.swift */; };
29285B421F6FB3E60044CF29 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29285B411F6FB3E60044CF29 /* SendViewController.swift */; };
293112101FC4ADCB00966EEA /* InCoordinatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931120F1FC4ADCB00966EEA /* InCoordinatorViewModel.swift */; };
293112121FC4F48400966EEA /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 293112111FC4F48400966EEA /* ServiceProvider.swift */; };
2931122E1FC94E4200966EEA /* SettingsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931122D1FC94E4200966EEA /* SettingsError.swift */; };
@ -287,6 +286,7 @@
5E7C70EEFB9D9745C6CF7578 /* ChooseTicketTransferModeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D177F5297D4A3DB3F33 /* ChooseTicketTransferModeViewController.swift */; };
5E7C70FF17622C0FFD45A542 /* AlphaWalletSettingPushRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D2AAB777BF35B8B56BD /* AlphaWalletSettingPushRow.swift */; };
5E7C710331196CD591B51785 /* LockCreatePasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */; };
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */; };
5E7C71A6B0BDF301747A49AE /* ScreenChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */; };
5E7C71A7D2BD6FCE3980CC51 /* ImportWalletHelpBubbleViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7A16ABC8BD5D508AA641 /* ImportWalletHelpBubbleViewViewModel.swift */; };
5E7C71B52A77008694BFA5D1 /* TokensDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74B9EB81C51E956566E7 /* TokensDataStore.swift */; };
@ -299,6 +299,7 @@
5E7C72AF95DCE8BC65490BCA /* StatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */; };
5E7C72B0A10A92E591696E48 /* ContactUsBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AE6FAE0DF969B4F52E9 /* ContactUsBannerView.swift */; };
5E7C72C8A15397C5A40BFE76 /* WhatIsEthereumInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C774BCA281E4B077DBBFA /* WhatIsEthereumInfoViewController.swift */; };
5E7C72E1D4B4B4C8443F3DA1 /* SendHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */; };
5E7C731B88842C036A74A039 /* AlphaWalletSettingsButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C71EBD4C95AD4E11F3352 /* AlphaWalletSettingsButtonRow.swift */; };
5E7C73305DF984B99E94D9F9 /* TransferModeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */; };
5E7C733638D7596F93DEE2A9 /* OnboardingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CE3F1D6B7993E7A840 /* OnboardingCollectionViewController.swift */; };
@ -307,6 +308,7 @@
5E7C745DACB5FCCEBCEB49CA /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C793E23E2364B73C4D813 /* WelcomeViewController.swift */; };
5E7C7499A8D6814F7950DA70 /* LockCreatePasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7AB3440C01136DF4F3E9 /* LockCreatePasscodeCoordinator.swift */; };
5E7C74B99922D0CAB635970E /* PasscodeCharacterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7B9220E616F82EDA956F /* PasscodeCharacterView.swift */; };
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C70088832B2D161EB4AAB /* SendViewController.swift */; };
5E7C75C99B9F595F26EDC405 /* LockPasscodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7D5F3CAE69CF932AB236 /* LockPasscodeViewController.swift */; };
5E7C75D46140FACBD12333BF /* EthTokenViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE374A74F2B00013C18 /* EthTokenViewCell.swift */; };
5E7C75E3C4BAE885746BD1B3 /* TransferTicketViaWalletAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */; };
@ -540,7 +542,6 @@
2923D9B41FDA4E07000CF3F8 /* PasswordGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGenerator.swift; sourceTree = "<group>"; };
2923D9B61FDA5E51000CF3F8 /* PasswordGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGeneratorTests.swift; sourceTree = "<group>"; };
29282B521F7630970067F88D /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
29285B411F6FB3E60044CF29 /* SendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; };
2931120F1FC4ADCB00966EEA /* InCoordinatorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InCoordinatorViewModel.swift; sourceTree = "<group>"; };
293112111FC4F48400966EEA /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; };
2931122D1FC94E4200966EEA /* SettingsError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsError.swift; sourceTree = "<group>"; };
@ -780,6 +781,7 @@
442FCFEB2D7443C4E0B889B0 /* TicketHolder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketHolder.swift; sourceTree = "<group>"; };
477899BEAA4489DA423E8857 /* Pods-TrustUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TrustUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TrustUITests/Pods-TrustUITests.debug.xcconfig"; sourceTree = "<group>"; };
4DB8204016307EAFC079EA48 /* Pods-Trust.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Trust.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Trust/Pods-Trust.debug.xcconfig"; sourceTree = "<group>"; };
5E7C70088832B2D161EB4AAB /* SendViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; };
5E7C7011D8E5C9FFE0E59D55 /* TransferTicketsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsViewController.swift; sourceTree = "<group>"; };
5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsQuantitySelectionViewModel.swift; sourceTree = "<group>"; };
5E7C70CC85B337061151724E /* ImportWalletHelpBubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletHelpBubbleView.swift; sourceTree = "<group>"; };
@ -822,6 +824,7 @@
5E7C778F20D32B70D7FF2135 /* TicketRedemptionInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketRedemptionInfoViewController.swift; sourceTree = "<group>"; };
5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenChecker.swift; sourceTree = "<group>"; };
5E7C7821694C489D5114DB18 /* TicketsViewControllerTitleHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketsViewControllerTitleHeader.swift; sourceTree = "<group>"; };
5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendHeaderView.swift; sourceTree = "<group>"; };
5E7C783E3ADA4CF9554A0E7D /* TicketTokenViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTokenViewCell.swift; sourceTree = "<group>"; };
5E7C78581AA28CA5C3CBC468 /* AdvancedSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsViewController.swift; sourceTree = "<group>"; };
5E7C78B001F9F95F404D5FEF /* HowDoIGetMyMoneyInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowDoIGetMyMoneyInfoViewController.swift; sourceTree = "<group>"; };
@ -841,6 +844,7 @@
5E7C7B0BE9EE3B198AE7D92D /* WhatIsASeedPhraseInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhatIsASeedPhraseInfoViewController.swift; sourceTree = "<group>"; };
5E7C7B1FB2702A2A8A4EBD76 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = "<group>"; };
5E7C7B3302309706CA0F972A /* TokensViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensViewController.swift; sourceTree = "<group>"; };
5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendHeaderViewViewModel.swift; sourceTree = "<group>"; };
5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewController.swift; sourceTree = "<group>"; };
5E7C7B9220E616F82EDA956F /* PasscodeCharacterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeCharacterView.swift; sourceTree = "<group>"; };
5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewControllerViewModel.swift; sourceTree = "<group>"; };
@ -1854,7 +1858,6 @@
29B6AECC1F7C87E700EC6DE3 /* ViewControllers */ = {
isa = PBXGroup;
children = (
29285B411F6FB3E60044CF29 /* SendViewController.swift */,
291A1B661F98092F00ADEC80 /* ConfirmPaymentViewController.swift */,
299B5E411FD2298E0051361C /* ConfigureTransactionViewController.swift */,
5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */,
@ -1862,6 +1865,7 @@
5E7C7419F47CC8B2996AA8F9 /* TransferTicketsQuantitySelectionViewController.swift */,
5E7C7D177F5297D4A3DB3F33 /* ChooseTicketTransferModeViewController.swift */,
5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */,
5E7C70088832B2D161EB4AAB /* SendViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
@ -1878,6 +1882,7 @@
5E7C703BA1D0E9ACB7399155 /* TransferTicketsQuantitySelectionViewModel.swift */,
5E7C75BE23CDD9CD271EC30C /* ChooseTicketTransferModeViewControllerViewModel.swift */,
5E7C7610C8DD3223230E3951 /* TransferTicketViaWalletAddressViewControllerViewModel.swift */,
5E7C7B7A45EDFA8ED1E25863 /* SendHeaderViewViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -2332,6 +2337,7 @@
isa = PBXGroup;
children = (
5E7C7717D829205D1E254AC1 /* TransferModeButton.swift */,
5E7C7828BD821B6F04B71C00 /* SendHeaderView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -3225,7 +3231,6 @@
296421951F70C1EC00EB363B /* LoadingView.swift in Sources */,
2961BD071FB146EB00C4B840 /* ChainState.swift in Sources */,
73D26837202E827E009777A1 /* DecimalFormatter.swift in Sources */,
29285B421F6FB3E60044CF29 /* SendViewController.swift in Sources */,
2996F14D1F6CA743005C33AE /* UIViewController.swift in Sources */,
2959961F1FAE759700DB66A8 /* RawTransaction.swift in Sources */,
295B61D41FE7D5B500642E60 /* CurrencyFormatter.swift in Sources */,
@ -3506,6 +3511,9 @@
5E7C7AD4DF6DFA6B3AF206E7 /* TransferTicketViaWalletAddressViewControllerViewModel.swift in Sources */,
5E7C7A41B07499B607476300 /* ScanQRCodeForWalletAddressToTransferTicketCoordinator.swift in Sources */,
5E7C7EEE563D81793CB96FA0 /* TransferTicketsCoordinator.swift in Sources */,
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */,
5E7C72E1D4B4B4C8443F3DA1 /* SendHeaderView.swift in Sources */,
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -42,8 +42,6 @@ class InCoordinator: Coordinator {
$0 as? TransactionCoordinator
}.first
}
//In addition to `transactionCoordinator` which is shown as a tab, `nonTabTransactionCoordinator` is meant for presenting (in iOS terms)
var nonTabTransactionCoordinator: TransactionCoordinator?
var ticketsCoordinator: TicketsCoordinator? {
return self.coordinators.flatMap {
@ -228,8 +226,6 @@ class InCoordinator: Coordinator {
func removeAllCoordinators() {
coordinators.removeAll()
//Manually remove nonTabTransactionCoordinator since we don't add it to coordinators because existing code assume there is only 1 TransactionCoordinator there
nonTabTransactionCoordinator = nil
}
func checkDevice() {
@ -312,45 +308,6 @@ class InCoordinator: Coordinator {
coordinator.showRedeemViewController()
}
private func showTransactions(for type: PaymentFlow) {
if nonTabTransactionCoordinator == nil {
if let wallet = keystore.recentlyUsedWallet {
let migration = MigrationInitializer(account: wallet, chainID: config.chainID)
let web3 = self.web3(for: config.server)
web3.start()
let realm = self.realm(for: migration.config)
let tokensStorage = TokensDataStore(realm: realm, account: wallet, config: config, web3: web3)
let balance = BalanceCoordinator(wallet: wallet, config: config, storage: tokensStorage)
let session = WalletSession(
account: wallet,
config: config,
web3: web3,
balanceCoordinator: balance
)
let transactionsStorage = TransactionsStorage(
realm: realm
)
nonTabTransactionCoordinator = TransactionCoordinator(
session: session,
storage: transactionsStorage,
keystore: keystore,
tokensStorage: tokensStorage
)
nonTabTransactionCoordinator?.delegate = self
nonTabTransactionCoordinator?.start()
nonTabTransactionCoordinator?.rootViewController.showActionButtons = true
nonTabTransactionCoordinator?.rootViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissTransactions))
}
}
guard let transactionCoordinator = nonTabTransactionCoordinator else {
return
}
transactionCoordinator.rootViewController.paymentType = type
navigationController.present(transactionCoordinator.navigationController, animated: true, completion: nil)
}
private func handlePendingTransaction(transaction: SentTransaction) {
transactionCoordinator?.dataCoordinator.addSentTransaction(transaction)
}
@ -432,7 +389,7 @@ extension InCoordinator: SettingsCoordinatorDelegate {
extension InCoordinator: TokensCoordinatorDelegate {
func didPress(for type: PaymentFlow, in coordinator: TokensCoordinator) {
showTransactions(for: type)
showPaymentFlow(for: type)
}
func didPressStormBird(for type: PaymentFlow, token: TokenObject, in coordinator: TokensCoordinator) {

@ -255,3 +255,7 @@
"a.claim.ticket.import.button.title" = "Import";
"a.setup.reminder.text.text" = "Please set up text messaging to send a text.";
"a.setup.reminder.email.text" = "Please set up a Mail account in order to send email.";
"a.send.receive.button.title" = "Send/Receive";
"a.send.recipient.address.title" = "ADDRESS";
"a.send.recipient.amount.title" = "AMOUNT TO TRANSFER";
"a.send.sender.address.title" = "MY ADDRESS";

@ -131,10 +131,6 @@ extension TransactionCoordinator: TransactionsViewControllerDelegate {
}
}
func didPressRequest(in viewController: TransactionsViewController) {
delegate?.didPress(for: .request, in: self)
}
func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController) {
showTransaction(transaction)
}

@ -9,7 +9,6 @@ import TrustKeystore
protocol TransactionsViewControllerDelegate: class {
func didPressSend(in viewController: TransactionsViewController)
func didPressRequest(in viewController: TransactionsViewController)
func didPressTransaction(transaction: Transaction, in viewController: TransactionsViewController)
func didPressDeposit(for account: Wallet, sender: UIView, in viewController: TransactionsViewController)
}
@ -43,7 +42,6 @@ class TransactionsViewController: UIViewController {
lazy var footerView: TransactionsFooterView = {
let footerView = TransactionsFooterView(frame: .zero)
footerView.translatesAutoresizingMaskIntoConstraints = false
footerView.requestButton.addTarget(self, action: #selector(request), for: .touchUpInside)
footerView.sendButton.addTarget(self, action: #selector(send), for: .touchUpInside)
return footerView
}()
@ -85,11 +83,6 @@ class TransactionsViewController: UIViewController {
let footerViewHeight = CGFloat(60)
footerBar.addSubview(footerView)
let separator = UIView()
separator.translatesAutoresizingMaskIntoConstraints = false
separator.backgroundColor = Colors.appLightButtonSeparator
footerBar.addSubview(separator)
actionButtonsVisibleConstraint = footerBar.heightAnchor.constraint(equalToConstant: footerViewHeight)
actionButtonsInVisibleConstraint = footerBar.topAnchor.constraint(equalTo: footerBar.bottomAnchor)
@ -107,11 +100,6 @@ class TransactionsViewController: UIViewController {
footerView.topAnchor.constraint(equalTo: footerBar.topAnchor),
footerView.heightAnchor.constraint(equalToConstant: footerViewHeight),
separator.leadingAnchor.constraint(equalTo: footerView.sendButton.trailingAnchor, constant: -separatorThickness / 2),
separator.trailingAnchor.constraint(equalTo: footerView.requestButton.leadingAnchor, constant: separatorThickness / 2),
separator.topAnchor.constraint(equalTo: footerView.topAnchor, constant: 8),
separator.bottomAnchor.constraint(equalTo: footerView.bottomAnchor, constant: -8),
footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
@ -174,10 +162,6 @@ class TransactionsViewController: UIViewController {
delegate?.didPressSend(in: self)
}
@objc func request() {
delegate?.didPressRequest(in: self)
}
func showDeposit(_ sender: UIButton) {
delegate?.didPressDeposit(for: account, sender: sender, in: self)
}

@ -8,23 +8,16 @@ class TransactionsFooterView: UIView {
lazy var sendButton: UIButton = {
let sendButton = UIButton(type: .system)
sendButton.translatesAutoresizingMaskIntoConstraints = false
sendButton.setTitle(NSLocalizedString("Send", value: "Send", comment: ""), for: .normal)
sendButton.setTitle(R.string.localizable.aSendReceiveButtonTitle(), for: .normal)
return sendButton
}()
lazy var requestButton: UIButton = {
let requestButton = UIButton(type: .system)
requestButton.translatesAutoresizingMaskIntoConstraints = false
requestButton.setTitle(R.string.localizable.transactionsReceiveButtonTitle(), for: .normal)
return requestButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
let stackView = UIStackView(arrangedSubviews: [
sendButton,
requestButton,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fillEqually
@ -51,9 +44,5 @@ class TransactionsFooterView: UIView {
sendButton.setTitleColor(Colors.appWhite, for: .normal)
sendButton.backgroundColor = Colors.appHighlightGreen
sendButton.titleLabel?.font = Fonts.regular(size: 20)!
requestButton.setTitleColor(Colors.appWhite, for: .normal)
requestButton.backgroundColor = Colors.appHighlightGreen
requestButton.titleLabel?.font = Fonts.regular(size: 20)!
}
}

@ -21,15 +21,6 @@ class PaymentCoordinator: Coordinator {
let storage: TokensDataStore
let ticketHolders: [TicketHolder]!
lazy var transferType: TransferType = {
switch self.flow {
case .send(let type):
return type
case .request:
return .ether(destination: .none)
}
}()
init(
navigationController: UINavigationController = UINavigationController(),
flow: PaymentFlow,

@ -46,6 +46,18 @@ class SendCoordinator: Coordinator {
}
func start() {
let config = Config()
let symbol = sendViewController.transferType.symbol(server: config.server)
sendViewController.configure(viewModel:
.init(transferType: sendViewController.transferType,
session: session,
storage: sendViewController.storage,
config: config,
currentPair: SendViewController.Pair(left: symbol, right: session.config.currency.rawValue)
)
)
//Make sure the pop up, especially the height, is enough to fit the content in iPad
sendViewController.preferredContentSize = CGSize(width: 540, height: 700)
if navigationController.viewControllers.isEmpty {
navigationController.viewControllers = [sendViewController]
} else {
@ -58,23 +70,15 @@ class SendCoordinator: Coordinator {
session: session,
storage: storage,
account: account,
transferType: transferType,
ticketHolders: ticketHolders
transferType: transferType
)
if navigationController.viewControllers.isEmpty {
controller.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismiss))
}
controller.navigationItem.rightBarButtonItem = UIBarButtonItem(
title: NSLocalizedString("Next", value: "Next", comment: ""),
style: .done,
target: controller,
action: #selector(SendViewController.send)
)
switch transferType {
case .ether(let destination):
controller.addressRow?.value = destination?.description
controller.addressRow?.cell.row.updateCell()
controller.targetAddressTextField.text = destination?.description
case .token: break
case .stormBird: break
case .stormBirdOrder: break

@ -1,4 +1,5 @@
// Copyright SIX DAY LLC. All rights reserved.
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
@ -8,6 +9,7 @@ import APIKit
import QRCodeReaderViewController
import BigInt
import TrustKeystore
import MBProgressHUD
protocol SendViewControllerDelegate: class {
func didPressConfirm(
@ -17,20 +19,43 @@ protocol SendViewControllerDelegate: class {
)
}
class SendViewController: FormViewController {
private lazy var viewModel: SendViewModel = {
return SendViewModel(transferType: self.transferType,
config: Config(),
ticketHolders: self.ticketHolders)
class SendViewController: UIViewController {
//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
let roundedBackground = UIView()
let header = SendHeaderView()
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 {
static let address = "address"
static let amount = "amount"
static let existingTicketIds = "existingTicketIds"
static let ticketIdsToSend = "ticketIdsToSend"
}
var viewModel: SendViewModel!
var headerViewModel = SendHeaderViewViewModel()
var balanceViewModel: BalanceBaseViewModel?
weak var delegate: SendViewControllerDelegate?
struct Pair {
let left: String
@ -46,17 +71,7 @@ class SendViewController: FormViewController {
let account: Account
let transferType: TransferType
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 = {
let decimalSeparator = Locale.current.decimalSeparator ?? "."
return "0123456789" + decimalSeparator
@ -69,122 +84,233 @@ class SendViewController: FormViewController {
lazy var decimalFormatter: DecimalFormatter = {
return DecimalFormatter()
}()
lazy var stringFormatter: StringFormatter = {
return StringFormatter()
}()
init(
session: WalletSession,
storage: TokensDataStore,
account: Account,
transferType: TransferType = .ether(destination: .none),
ticketHolders: [TicketHolder] = []
transferType: TransferType = .ether(destination: .none)
) {
self.session = session
self.account = account
self.transferType = transferType
self.storage = storage
self.ticketHolders = ticketHolders
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()
getGasPrice()
}
if viewModel.isStormBird {
title = viewModel.title
} 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)
@objc func closeKeyboard() {
view.endEditing(true)
}
let fiatButton = Button(size: .normal, style: .borderless)
fiatButton.translatesAutoresizingMaskIntoConstraints = false
fiatButton.setTitle(currentPair.right, for: .normal)
fiatButton.addTarget(self, action: #selector(fiatAction), for: .touchUpInside)
fiatButton.isHidden = isFiatViewHidden()
func configure(viewModel: SendViewModel) {
let firstConfigure = self.viewModel == nil
self.viewModel = viewModel
let amountRightView = UIStackView(arrangedSubviews: [
fiatButton,
])
if firstConfigure {
//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
amountRightView.distribution = .equalSpacing
amountRightView.spacing = 1
amountRightView.axis = .horizontal
changeQRCode(value: 0)
if viewModel.isStormBird {
form += [Section(viewModel.formHeaderTitle)
<<< TextAreaRow(Values.existingTicketIds) {
$0.textAreaHeight = .dynamic(initialTextViewHeight: 44)
$0.value = viewModel.ticketNumbers
}.cellUpdate { cell, _ in
cell.isUserInteractionEnabled = false
},
]
}
view.backgroundColor = viewModel.backgroundColor
form += [Section(footer: formFooterText())
<<< AppFormAppearance.textFieldFloat(tag: Values.address) {
$0.add(rule: EthereumAddressRule())
$0.validationOptions = .validatesOnDemand
}.cellUpdate { cell, _ in
cell.textField.textAlignment = .left
cell.textField.placeholder = NSLocalizedString("send.recipientAddress.textField.placeholder", value: "Recipient Address", comment: "")
cell.textField.rightView = recipientRightView
cell.textField.rightViewMode = .always
cell.textField.accessibilityIdentifier = "amount-field"
}
<<< AppFormAppearance.textFieldFloat(tag: Values.amount) {
$0.add(rule: RuleClosure<String> { [weak self] rowValue in
return !(self?.viewModel.isStormBird)! && (rowValue == nil || rowValue!.isEmpty) ? ValidationError(msg: "Field required!") : nil
})
$0.validationOptions = .validatesOnDemand
$0.hidden = Condition(booleanLiteral: self.viewModel.isStormBird)
}.cellUpdate { [weak self] cell, _ in
cell.textField.isCopyPasteDisabled = true
cell.textField.textAlignment = .left
cell.textField.delegate = self
cell.textField.placeholder = "\(self?.currentPair.left ?? "") " + NSLocalizedString("send.amount.textField.placeholder", value: "Amount", comment: "")
cell.textField.keyboardType = .decimalPad
cell.textField.rightView = amountRightView
cell.textField.rightViewMode = .always
}
<<< AppFormAppearance.textFieldFloat(tag: Values.ticketIdsToSend) {
$0.add(rule: RuleClosure<String> { [weak self] rowValue in
if (self?.viewModel.isStormBird)! {
if !(self?.ticketIdsValidated())! {
return ValidationError(msg: "Please enter valid ticket IDs!")
}
}
return nil
})
$0.validationOptions = .validatesOnDemand
$0.hidden = Condition(booleanLiteral: !self.viewModel.isStormBird)
}.cellUpdate { cell, _ in
cell.textField.isCopyPasteDisabled = true
cell.textField.textAlignment = .left
cell.textField.placeholder = NSLocalizedString("send.amount.textField.ticketids", value: "Enter Ticket IDs", comment: "")
cell.textField.keyboardType = .numbersAndPunctuation
},
]
header.configure(viewModel: headerViewModel)
targetAddressTextField.textColor = viewModel.textFieldTextColor
targetAddressTextField.font = viewModel.textFieldFont
targetAddressTextField.layer.borderColor = viewModel.textFieldBorderColor.cgColor
targetAddressTextField.layer.borderWidth = viewModel.textFieldBorderWidth
targetAddressLabel.text = R.string.localizable.aSendRecipientAddressTitle()
targetAddressLabel.font = viewModel.textFieldsLabelFont
targetAddressLabel.textColor = viewModel.textFieldsLabelTextColor
amountLabel.text = R.string.localizable.aSendRecipientAmountTitle()
amountLabel.font = viewModel.textFieldsLabelFont
amountLabel.textColor = viewModel.textFieldsLabelTextColor
amountTextField.textColor = viewModel.textFieldTextColor
amountTextField.font = viewModel.textFieldFont
amountTextField.layer.borderColor = viewModel.textFieldBorderColor.cgColor
amountTextField.layer.borderWidth = viewModel.textFieldBorderWidth
alternativeAmountLabel.numberOfLines = 0
alternativeAmountLabel.textColor = viewModel.alternativeAmountColor
alternativeAmountLabel.font = viewModel.alternativeAmountFont
alternativeAmountLabel.textAlignment = .center
alternativeAmountLabel.text = viewModel.alternativeAmountText
alternativeAmountLabel.isHidden = !viewModel.showAlternativeAmount
myAddressLabelLabel.text = R.string.localizable.aSendSenderAddressTitle()
myAddressLabelLabel.font = viewModel.textFieldsLabelFont
myAddressLabelLabel.textColor = viewModel.textFieldsLabelTextColor
myAddressLabel.textColor = viewModel.myAddressTextColor
myAddressLabel.font = viewModel.addressFont
myAddressLabel.text = viewModel.myAddressText
copyButton.titleLabel?.font = viewModel.copyAddressButtonFont
copyButton.setTitle(" \(viewModel.copyAddressButtonTitle) ", for: .normal)
copyButton.setTitleColor(viewModel.copyAddressButtonTitleColor, for: .normal)
copyButton.backgroundColor = viewModel.copyAddressButtonBackgroundColor
myAddressContainer.borderColor = viewModel.myAddressBorderColor
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) {
super.viewWillAppear(animated)
self.navigationController?.applyTintAdjustment()
private func roundCornersBasedOnHeight() {
targetAddressTextField.layer.cornerRadius = targetAddressTextField.frame.size.height / 2
amountTextField.layer.cornerRadius = amountTextField.frame.size.height / 2
copyButton.cornerRadius = copyButton.frame.size.height / 2
}
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() {
let errors = form.validate()
guard errors.isEmpty else {
return
}
let addressString = addressRow?.value?.trimmed ?? ""
let addressString = targetAddressTextField.text?.trimmed ?? ""
var amountString = ""
if self.currentPair.left == viewModel.symbol {
amountString = amountRow?.value?.trimmed ?? ""
amountString = amountTextField.text?.trimmed ?? ""
} else {
guard let formatedValue = decimalFormatter.string(from: NSNumber(value: self.pairValue)) else {
return displayError(error: SendInputErrors.wrongInput)
@ -252,7 +366,7 @@ class SendViewController: FormViewController {
r: .none,
s: .none,
expiry: .none,
indices: viewModel.isStormBird ? getIndiciesFromUI() : .none
indices: .none
)
self.delegate?.didPressConfirm(transaction: transaction, transferType: transferType, in: self)
}
@ -271,28 +385,24 @@ class SendViewController: FormViewController {
guard CryptoAddressValidator.isValidAddress(value) else {
return displayError(error: Errors.invalidAddress)
}
addressRow?.value = "0x99f05a668119d8938d79f85add73c9ab8ff719b1"
addressRow?.reload()
targetAddressTextField.text = value
activateAmountView()
}
@objc func useMaxAmount() {
guard let value = session.balance?.amountFull else {
return
}
amountRow?.value = value
amountRow?.reload()
}
@objc func fiatAction(sender: UIButton) {
let swappedPair = currentPair.swapPair()
//New pair for future calculation we should swap pair each time we press fiat button.
self.currentPair = swappedPair
if var viewModel = viewModel {
viewModel.currentPair = currentPair
viewModel.pairValue = 0
configure(viewModel: viewModel)
}
//Update button title.
sender.setTitle(currentPair.right, for: .normal)
//Reset amountRow value.
amountRow?.value = nil
amountRow?.reload()
sender.setTitle(currentPair.left, for: .normal)
amountTextField.text = nil
//Reset pair value.
pairValue = 0.0
//Update section.
@ -301,8 +411,17 @@ class SendViewController: FormViewController {
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() {
amountRow?.cell.textField.becomeFirstResponder()
amountTextField.becomeFirstResponder()
}
required init?(coder aDecoder: NSCoder) {
@ -310,87 +429,158 @@ class SendViewController: FormViewController {
}
private func updatePriceSection() {
//Update section only if fiat view is visible.
guard !isFiatViewHidden() else {
guard viewModel.showAlternativeAmount else {
return
}
//We use this section update to prevent update of the all section including cells.
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
let footerSectionIndex: Int
if viewModel.isStormBird {
footerSectionIndex = 1
} else {
footerSectionIndex = 0
}
if let containerView = tableView.footerView(forSection: footerSectionIndex) {
containerView.textLabel!.text = valueOfPairRepresantetion()
containerView.sizeToFit()
if var viewModel = viewModel {
viewModel.pairValue = pairValue
configure(viewModel: viewModel)
}
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
}
private func updatePairPrice(with amount: Double) {
guard let rates = storage.tickers, let currentTokenInfo = rates[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price) else {
return
}
if self.currentPair.left == viewModel.symbol {
if currentPair.left == viewModel.symbol {
pairValue = amount * price
} else {
pairValue = amount / price
}
self.updatePriceSection()
updatePriceSection()
}
private func addressTextFieldChanged(in range: NSRange, to string: String) -> Bool {
return true
}
private func isFiatViewHidden() -> Bool {
guard let currentTokenInfo = storage.tickers?[viewModel.destinationAddress.description], let price = Double(currentTokenInfo.price), price > 0 else {
private func amountTextFieldChanged(in range: NSRange, to string: String) -> Bool {
guard let input = amountTextField.text else {
return true
}
return false
//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 formFooterText() -> String {
return isFiatViewHidden() ? "" : valueOfPairRepresantetion()
private func changeQRCode(value: Int) {
if let viewModel = viewModel {
let string = viewModel.myAddressText
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 getTicket(for id: UInt16) -> Ticket? {
let tickets = ticketHolders.flatMap { $0.tickets }
let filteredTickets = tickets.filter { $0.id == id }
return filteredTickets.first
private func generateQRCode(from string: String) -> UIImage? {
return string.toQRCode()
}
private func isTicketExisting(for id: UInt16) -> Bool {
return getTicket(for: id) != nil
private func configureBalanceViewModel() {
switch transferType {
case .ether:
session.balanceViewModel.subscribe { viewModel in
guard let viewModel = viewModel else { return }
let amount = viewModel.amountShort
self.headerViewModel.title = "\(amount) \(self.session.config.server.name) (\(viewModel.symbol))"
if let viewModel = self.viewModel {
self.configure(viewModel: viewModel)
}
}
session.refresh(.ethBalance)
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
}
}
private func getTicketIds() -> [String] {
return (ticketIdsRow?.value?.components(separatedBy: ","))!
private func makeTargetAddressRightView() -> UIView {
let pasteButton = Button(size: .normal, style: .borderless)
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 ticketIdsValidated() -> Bool {
let rowValue = ticketIdsRow?.value
if rowValue == nil || rowValue!.isEmpty {
return false
}
let ticketIds = getTicketIds()
for id in ticketIds {
guard id.isNumeric() else {
return false
}
guard let intId = UInt16(id) else {
return false
}
guard isTicketExisting(for: intId) else {
return false
}
}
return true
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 getIndiciesFromUI() -> [UInt16] {
let ticketIds = getTicketIds()
return ticketIds.map { (getTicket(for: UInt16($0)!)?.index)! }
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 +599,7 @@ extension SendViewController: QRCodeReaderDelegate {
guard let result = QRURLParser.from(string: result) else {
return
}
addressRow?.value = result.address
addressRow?.reload()
targetAddressTextField.text = result.address
if let dataString = result.params["data"] {
data = Data(hex: dataString.drop0x)
@ -419,50 +608,32 @@ extension SendViewController: QRCodeReaderDelegate {
}
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 {
amountRow?.value = ""
amountTextField.text = ""
}
amountRow?.reload()
pairValue = 0.0
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 {
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
}
//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)
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
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == targetAddressTextField {
activateAmountView()
} else if textField == amountTextField {
view.endEditing(true)
}
self.updatePairPrice(with: amount.doubleValue)
return true
}
}

@ -0,0 +1,89 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
struct SendHeaderViewViewModel {
var title = ""
var issuer: String {
return ""
}
var blockChainName: String {
return "Ethereum Blockchain"
}
var backgroundColor: UIColor {
return Colors.appWhite
}
var contentsBackgroundColor: UIColor {
return Colors.appWhite
}
var titleColor: UIColor {
return Colors.appText
}
var subtitleColor: UIColor {
return Colors.appBackground
}
var titleFont: UIFont {
return Fonts.light(size: 25)!
}
var subtitleFont: UIFont {
return Fonts.semibold(size: 10)!
}
var borderColor: UIColor {
return UIColor(red: 236, green: 236, blue: 236)
}
var textColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var valuePercentageChangeColor: UIColor {
//TODO must have a different color when depreciate?
return Colors.appHighlightGreen
}
var textValueFont: UIFont {
return Fonts.semibold(size: 15)!
}
var textLabelFont: UIFont {
return Fonts.regular(size: 10)!
}
var valuePercentageChangeValue: String {
//TODO read from model
// return "+50%"
return "N/A"
}
var valuePercentageChangePeriod: String {
return R.string.localizable.aWalletContentsValuePeriodTitle()
}
var valueChange: String {
//TODO read from model
// return "$17,000"
return "N/A"
}
var valueChangeName: String {
return R.string.localizable.aWalletContentsValueAppreciationTitle()
}
var value: String {
//TODO read from model
return "N/A"
}
var valueName: String {
return R.string.localizable.aWalletContentsValueDollarTitle()
}
}

@ -5,26 +5,22 @@ import UIKit
import TrustKeystore
struct SendViewModel {
typealias Pair = SendViewController.Pair
let transferType: TransferType
let session: WalletSession
let storage: TokensDataStore
let config: Config
let ticketHolders: [TicketHolder]!
var currentPair: Pair
let stringFormatter = StringFormatter()
var pairValue = 0.0
var title: String {
return isStormBird ? "Transfer Ticket" : "Send" + symbol
}
var formHeaderTitle: String {
if let ticketHolder = ticketHolders.first {
return ticketHolder.name
}
return ""
}
var ticketNumbers: String {
let tickets = ticketHolders.flatMap { $0.tickets }
let ids = tickets.map { String($0.id) }
return ids.joined(separator: ",")
init(transferType: TransferType, session: WalletSession, storage: TokensDataStore, config: Config, currentPair: Pair) {
self.transferType = transferType
self.session = session
self.storage = storage
self.config = config
self.currentPair = currentPair
}
var symbol: String {
@ -36,14 +32,7 @@ struct SendViewModel {
}
var backgroundColor: UIColor {
return .white
}
var isStormBird: Bool {
if let token = self.token {
return token.isStormBird
}
return false
return Colors.appBackground
}
var token: TokenObject? {
@ -59,4 +48,102 @@ struct SendViewModel {
}
}
var textFieldTextColor: UIColor {
return Colors.appText
}
var textFieldFont: UIFont {
if ScreenChecker().isNarrowScreen() {
return Fonts.light(size: 11)!
} else {
return Fonts.light(size: 15)!
}
}
var textFieldBorderColor: UIColor {
return Colors.appBackground
}
var textFieldBorderWidth: CGFloat {
return 1
}
var textFieldHorizontalPadding: CGFloat {
return 22
}
var alternativeAmountColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var alternativeAmountFont: UIFont {
return Fonts.regular(size: 10)!
}
var alternativeAmountText: String {
return valueOfPairRepresentation()
}
var showAlternativeAmount: Bool {
guard let currentTokenInfo = storage.tickers?[destinationAddress.description], let price = Double(currentTokenInfo.price), price > 0 else {
return false
}
return true
}
private func valueOfPairRepresentation() -> String {
var formattedString = ""
if currentPair.left == symbol {
formattedString = StringFormatter().currency(with: pairValue, and: session.config.currency.rawValue)
} else {
formattedString = stringFormatter.formatter(for: pairValue)
}
return "~ \(formattedString) " + "\(currentPair.right)"
}
var myAddressText: String {
return session.account.address.description
}
var addressColor: UIColor {
return Colors.appText
}
var addressFont: UIFont {
return Fonts.semibold(size: 14)!
}
var addressCopiedText: String {
return NSLocalizedString("request.addressCopied.title", value: "Address copied", comment: "")
}
var copyAddressButtonBackgroundColor: UIColor {
return Colors.appBackground
}
var copyAddressButtonTitleColor: UIColor {
return Colors.appWhite
}
var copyAddressButtonFont: UIFont {
return Fonts.regular(size: 14)!
}
var copyAddressButtonTitle: String {
return R.string.localizable.copy()
}
var textFieldsLabelTextColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var textFieldsLabelFont: UIFont {
return Fonts.regular(size: 10)!
}
var myAddressTextColor: UIColor {
return Colors.gray
}
var myAddressBorderColor: UIColor {
return UIColor(red: 235, green: 235, blue: 235)
}
var myAddressBorderWidth: CGFloat {
return 1
}
var buttonTitleColor: UIColor {
return Colors.appWhite
}
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
}

@ -0,0 +1,132 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
class SendHeaderView: UIView {
let background = UIView()
let titleLabel = UILabel()
let blockchainLabel = UILabel()
let issuerLabel = UILabel()
let middleBorder = UIView()
let valuePercentageChangeValueLabel = UILabel()
let valuePercentageChangePeriodLabel = UILabel()
let valueChangeLabel = UILabel()
let valueChangeNameLabel = UILabel()
let valueLabel = UILabel()
let valueNameLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
background.translatesAutoresizingMaskIntoConstraints = false
addSubview(background)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
valuePercentageChangeValueLabel.textAlignment = .center
valuePercentageChangePeriodLabel.textAlignment = .center
valueChangeLabel.textAlignment = .center
valueChangeNameLabel.textAlignment = .center
valueLabel.textAlignment = .center
valueNameLabel.textAlignment = .center
let bottomRowStack = UIStackView(arrangedSubviews: [blockchainLabel, issuerLabel])
bottomRowStack.axis = .horizontal
bottomRowStack.spacing = 15
bottomRowStack.distribution = .fill
let footerValuesStack = UIStackView(arrangedSubviews: [valuePercentageChangeValueLabel, valueChangeLabel, valueLabel])
footerValuesStack.axis = .horizontal
footerValuesStack.spacing = 15
footerValuesStack.distribution = .fillEqually
let footerNamesStack = UIStackView(arrangedSubviews: [valuePercentageChangePeriodLabel, valueChangeNameLabel, valueNameLabel])
footerNamesStack.axis = .horizontal
footerNamesStack.spacing = 15
footerNamesStack.distribution = .fillEqually
let footerStackView = UIStackView(arrangedSubviews: [middleBorder, .spacer(height: 14), footerValuesStack, footerNamesStack])
footerStackView.translatesAutoresizingMaskIntoConstraints = false
footerStackView.axis = .vertical
footerStackView.spacing = 0
footerStackView.distribution = .fill
footerValuesStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
bottomRowStack,
.spacer(height: 7),
footerStackView,
])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 0
stackView.distribution = .fill
background.addSubview(stackView)
let backgroundWidthConstraint = background.widthAnchor.constraint(equalTo: widthAnchor)
backgroundWidthConstraint.priority = .defaultHigh
// TODO extract constant. Maybe StyleLayout.sideMargin
NSLayoutConstraint.activate([
background.leadingAnchor.constraint(equalTo: leadingAnchor),
background.topAnchor.constraint(equalTo: topAnchor),
background.heightAnchor.constraint(equalTo: heightAnchor),
backgroundWidthConstraint,
middleBorder.heightAnchor.constraint(equalToConstant: 1),
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 7),
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -7),
stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: 16),
stackView.bottomAnchor.constraint(lessThanOrEqualTo: background.bottomAnchor, constant: -16),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: SendHeaderViewViewModel) {
backgroundColor = viewModel.backgroundColor
titleLabel.textColor = viewModel.titleColor
titleLabel.font = viewModel.titleFont
titleLabel.text = viewModel.title
titleLabel.adjustsFontSizeToFitWidth = true
blockchainLabel.textColor = viewModel.subtitleColor
blockchainLabel.font = viewModel.subtitleFont
blockchainLabel.text = viewModel.blockChainName
issuerLabel.textColor = viewModel.subtitleColor
issuerLabel.font = viewModel.subtitleFont
let issuer = viewModel.issuer
if issuer.isEmpty {
issuerLabel.text = ""
} else {
issuerLabel.text = "\(R.string.localizable.aWalletContentsIssuerTitle()): \(issuer)"
}
middleBorder.backgroundColor = viewModel.borderColor
valuePercentageChangePeriodLabel.textColor = viewModel.textColor
valuePercentageChangePeriodLabel.font = viewModel.textLabelFont
valuePercentageChangePeriodLabel.text = viewModel.valuePercentageChangePeriod
valueChangeNameLabel.textColor = viewModel.textColor
valueChangeNameLabel.font = viewModel.textLabelFont
valueChangeNameLabel.text = viewModel.valueChangeName
valueNameLabel.textColor = viewModel.textColor
valueNameLabel.font = viewModel.textLabelFont
valueNameLabel.text = viewModel.valueName
valuePercentageChangeValueLabel.textColor = viewModel.valuePercentageChangeColor
valuePercentageChangeValueLabel.font = viewModel.textValueFont
valuePercentageChangeValueLabel.text = viewModel.valuePercentageChangeValue
valueChangeLabel.textColor = viewModel.textColor
valueChangeLabel.font = viewModel.textValueFont
valueChangeLabel.text = viewModel.valueChange
valueLabel.textColor = viewModel.textColor
valueLabel.font = viewModel.textValueFont
valueLabel.text = viewModel.value
}
}

@ -33,7 +33,7 @@ class SendCoordinatorTests: XCTestCase {
)
coordinator.start()
XCTAssertEqual(address.description, coordinator.sendViewController.addressRow?.value)
XCTAssertEqual(address.description, coordinator.sendViewController.targetAddressTextField.text)
XCTAssertTrue(coordinator.navigationController.viewControllers[0] is SendViewController)
}

Loading…
Cancel
Save