Merge pull request #179 from James-Sangalli/change-import-ticket-ui

Update import ticket UI
pull/177/merge
James Sangalli 7 years ago committed by GitHub
commit e0c07a15bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      Trust.xcodeproj/project.pbxproj
  2. 14
      Trust/AppDelegate.swift
  3. 9
      Trust/Localization/en.lproj/Localizable.strings
  4. 109
      Trust/Market/Coordinators/UniversalLinkCoordinator.swift
  5. 268
      Trust/Market/ViewControllers/ImportTicketViewController.swift
  6. 264
      Trust/Market/ViewModels/ImportTicketViewControllerViewModel.swift

@ -303,6 +303,7 @@
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 */; };
5E7C7376B566E5A59CC8F463 /* ImportTicketViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72D0E7CA03ADE5CFAE7A /* ImportTicketViewControllerViewModel.swift */; };
5E7C73FC3990D110C474C3D6 /* WalletFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75CC640BAFFE0E789F44 /* WalletFilterViewModel.swift */; };
5E7C73FD5BD75D90C8D0EF3C /* WalletFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7C58586099F082973073 /* WalletFilterView.swift */; };
5E7C745DACB5FCCEBCEB49CA /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C793E23E2364B73C4D813 /* WelcomeViewController.swift */; };
@ -314,6 +315,7 @@
5E7C75E3C4BAE885746BD1B3 /* TransferTicketViaWalletAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */; };
5E7C75F80A7E178B49830BCD /* TicketsViewControllerHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C796039C0F47CDCA236C0 /* TicketsViewControllerHeader.swift */; };
5E7C760C7D55C97424F55138 /* TicketTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C75F877B2F2E24C7EF258 /* TicketTableViewCellViewModel.swift */; };
5E7C76605A5102FBD376F32A /* ImportTicketViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7535095323B035CA47C0 /* ImportTicketViewController.swift */; };
5E7C76A0365D128B7F19A0C2 /* ProtectionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C74BEC095303B66FB4B1E /* ProtectionCoordinator.swift */; };
5E7C76A65C14D0F11AF7848F /* TicketRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7742709724B3BD0C2A0D /* TicketRowViewModel.swift */; };
5E7C76B917517C93D1E26B0A /* LockEnterPasscodeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7981AB6584B25C72D46B /* LockEnterPasscodeCoordinator.swift */; };
@ -793,6 +795,7 @@
5E7C71EBD4C95AD4E11F3352 /* AlphaWalletSettingsButtonRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsButtonRow.swift; path = Views/AlphaWalletSettingsButtonRow.swift; sourceTree = "<group>"; };
5E7C72142D5817EF8FA8CADA /* PrivacyPolicyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyViewController.swift; sourceTree = "<group>"; };
5E7C72B37551451352EBB9F9 /* TransferTicketViaWalletAddressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketViaWalletAddressViewController.swift; sourceTree = "<group>"; };
5E7C72D0E7CA03ADE5CFAE7A /* ImportTicketViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportTicketViewControllerViewModel.swift; sourceTree = "<group>"; };
5E7C731B6F01534683227123 /* TicketTokenViewCellViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TicketTokenViewCellViewModel.swift; sourceTree = "<group>"; };
5E7C73495E0C0A207152EC25 /* LockEnterPasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LockEnterPasscodeViewController.swift; path = Trust/AlphaWalletLock/ViewControllers/LockEnterPasscodeViewController.swift; sourceTree = SOURCE_ROOT; };
5E7C741196D9D9C9C3EE5E30 /* LockCreatePasscodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockCreatePasscodeViewController.swift; sourceTree = "<group>"; };
@ -804,6 +807,7 @@
5E7C74DCC21272EC231A20E2 /* RequestViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestViewController.swift; sourceTree = "<group>"; };
5E7C74FABE14B7B1BEEC4F5E /* WhyUseEthereumInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WhyUseEthereumInfoViewController.swift; sourceTree = "<group>"; };
5E7C7534FB6BF4D199643246 /* AlphaWalletSettingsSwitchRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlphaWalletSettingsSwitchRow.swift; path = Views/AlphaWalletSettingsSwitchRow.swift; sourceTree = "<group>"; };
5E7C7535095323B035CA47C0 /* ImportTicketViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportTicketViewController.swift; sourceTree = "<group>"; };
5E7C755132D9B6F95080A1BE /* TransferTicketsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferTicketsCoordinator.swift; sourceTree = "<group>"; };
5E7C7564AF453BAB0BDAAA57 /* SettingsAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAction.swift; sourceTree = "<group>"; };
5E7C75BE23CDD9CD271EC30C /* ChooseTicketTransferModeViewControllerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChooseTicketTransferModeViewControllerViewModel.swift; sourceTree = "<group>"; };
@ -2272,6 +2276,7 @@
isa = PBXGroup;
children = (
5E7C7B82CC07F290B9CAA4E4 /* StatusViewController.swift */,
5E7C7535095323B035CA47C0 /* ImportTicketViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
@ -2305,6 +2310,7 @@
isa = PBXGroup;
children = (
5E7C7BD9B4BDAFC2D9EBD741 /* StatusViewControllerViewModel.swift */,
5E7C72D0E7CA03ADE5CFAE7A /* ImportTicketViewControllerViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
@ -3514,6 +3520,8 @@
5E7C7567A690B6B8F889AE83 /* SendViewController.swift in Sources */,
5E7C72E1D4B4B4C8443F3DA1 /* SendHeaderView.swift in Sources */,
5E7C713ACE8C72642B1C9F93 /* SendHeaderViewViewModel.swift in Sources */,
5E7C7376B566E5A59CC8F463 /* ImportTicketViewControllerViewModel.swift in Sources */,
5E7C76605A5102FBD376F32A /* ImportTicketViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -9,6 +9,8 @@ import RealmSwift
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
var coordinator: AppCoordinator!
// Need to retain while still processing
var universalLinkCoordinator: UniversalLinkCoordinator!
//This is separate coordinator for the protection of the sensitive information.
lazy var protectionCoordinator: ProtectionCoordinator = {
return ProtectionCoordinator()
@ -82,10 +84,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
Branch.getInstance().continue(userActivity)
let url = userActivity.webpageURL
let coordinator = UniversalLinkCoordinator()
coordinator.delegate = self
coordinator.start()
let handled = coordinator.handleUniversalLink(url: url)
universalLinkCoordinator = UniversalLinkCoordinator()
universalLinkCoordinator.delegate = self
universalLinkCoordinator.start()
let handled = universalLinkCoordinator.handleUniversalLink(url: url)
//TODO: if we handle other types of URLs, check if handled==false, then we pass the url to another handlers
return true
@ -103,4 +105,8 @@ extension AppDelegate: UniversalLinkCoordinatorDelegate {
return nil
}
}
func completed(in coordinator: UniversalLinkCoordinator) {
universalLinkCoordinator = nil
}
}

@ -248,11 +248,18 @@
"a.welcome.onboarding.createwallet.button.title" = "GET STARTED";
"a.settings.advanced.label.title" = "Advanced";
"a.marketplace.tabbar.item.title" = "Marketplace";
"a.claim.ticket.title" = "Importing tickets";
"a.claim.ticket.success.title" = "Your ticket has been transferred and the balance will be updated shortly";
"a.claim.ticket.failed.title" = "Invalid ticket link";
"a.claim.ticket.inProgress.title" = "Importing ticket...";
"a.claim.ticket.validating.title" = "Processing...";
"a.claim.ticket.promptImport.title" = "Import?";
"a.claim.ticket.inProgress.title" = "Importing tickets...";
"a.claim.ticket.done.button.title" = "Done";
"a.claim.ticket.import.button.title" = "Import";
"a.claim.ticket.purchase.button.title" = "Import";
"a.claim.ticket.ethCostLabel.title" = "Total Cost";
"a.claim.ticket.ethCost.free.title" = "Free Import";
"a.claim.ticket.dollarCostLabel.title" = "EQUIVALENT IN USD";
"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";

@ -5,14 +5,16 @@ import Alamofire
protocol UniversalLinkCoordinatorDelegate: class {
func viewControllerForPresenting(in coordinator: UniversalLinkCoordinator) -> UIViewController?
func completed(in coordinator: UniversalLinkCoordinator)
}
class UniversalLinkCoordinator: Coordinator {
var coordinators: [Coordinator] = []
weak var delegate: UniversalLinkCoordinatorDelegate?
var statusViewController: StatusViewController?
var importTicketViewController: ImportTicketViewController?
func start() {
preparingToImportUniversalLink()
}
//Returns true if handled
@ -22,7 +24,7 @@ class UniversalLinkCoordinator: Coordinator {
return false
}
let keystore = try! EtherKeystore()
let signedOrder = UniversalLinkHandler().parseUniversalLink(url: (url?.absoluteString)!)
let signedOrder = UniversalLinkHandler().parseUniversalLink(url: (url?.absoluteString)!)
let signature = signedOrder.signature.substring(from: 2)
// form the json string out of the order for the paymaster server
@ -35,50 +37,83 @@ class UniversalLinkCoordinator: Coordinator {
}
//cut off last comma
indicesStringEncoded = indicesStringEncoded.substring(to: indicesStringEncoded.count - 1)
let address = (keystore.recentlyUsedWallet?.address.eip55String)!
let address = (keystore.recentlyUsedWallet?.address.eip55String)!
let parameters: Parameters = [
"address": address,
"address": address,
"indices": indicesStringEncoded,
"expiry": signedOrder.order.expiry.description,
"expiry": signedOrder.order.expiry.description,
"v": signature.substring(from: 128),
"r": "0x" + signature.substring(with: Range(uncheckedBounds: (0, 64))),
"s": "0x" + signature.substring(with: Range(uncheckedBounds: (64, 128)))
]
let query = UniversalLinkHandler.paymentServer
let query = UniversalLinkHandler.paymentServer
//TODO check if URL is valid or not by validating signature, low priority
//TODO localize
if signature.count > 128 {
if let viewController = delegate?.viewControllerForPresenting(in: self) {
UIAlertController.alert(title: nil, message: "Import Link?", alertButtonTitles: [R.string.localizable.aClaimTicketImportButtonTitle(), R.string.localizable.cancel()], alertButtonStyles: [.default, .cancel], viewController: viewController) {
if $0 == 0 {
self.importUniversalLink(query: query, parameters: parameters)
}
}
}
//TODO create Ticket instances and 1 TicketHolder instance and compute cost from link's information
let ticket = Ticket(id: 1, index: 1, zone: "", name: "", venue: "", date: Date(), seatId: 1)
let ticketHolder = TicketHolder(
tickets: [ticket],
zone: "ABC",
name: "Applying for mortages (APM)",
venue: "XYZ Stadium",
date: Date(),
status: .available
)
//nil or "" implies free
let ethCost = "0.00001"
let dollarCost = "0.004"
if let vc = importTicketViewController {
vc.query = query
vc.parameters = parameters
}
self.promptImportUniversalLink(ticketHolder: ticketHolder, ethCost: ethCost, dollarCost: dollarCost)
} else {
return true
//TODO Pass in error message
self.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedTitle())
}
return true
}
private func importUniversalLink(query: String, parameters: Parameters) {
private func preparingToImportUniversalLink() {
if let viewController = delegate?.viewControllerForPresenting(in: self) {
statusViewController = StatusViewController()
if let vc = statusViewController {
importTicketViewController = ImportTicketViewController()
if let vc = importTicketViewController {
vc.delegate = self
vc.configure(viewModel: .init(
state: .processing,
inProgressText: R.string.localizable.aClaimTicketInProgressTitle(),
succeededTextText: R.string.localizable.aClaimTicketSuccessTitle(),
failedText: R.string.localizable.aClaimTicketFailedTitle()
))
vc.modalPresentationStyle = .overCurrentContext
viewController.present(vc, animated: true)
vc.configure(viewModel: .init(state: .validating))
viewController.present(UINavigationController(rootViewController: vc), animated: true)
}
}
}
private func updateImportTicketController(with state: ImportTicketViewControllerViewModel.State, ticketHolder: TicketHolder? = nil, ethCost: String? = nil, dollarCost: String? = nil) {
if let vc = importTicketViewController, var viewModel = vc.viewModel {
viewModel.state = state
if let ticketHolder = ticketHolder, let ethCost = ethCost, let dollarCost = dollarCost {
viewModel.ticketHolder = ticketHolder
viewModel.ethCost = ethCost
viewModel.dollarCost = dollarCost
}
vc.configure(viewModel: viewModel)
}
}
private func promptImportUniversalLink(ticketHolder: TicketHolder, ethCost: String, dollarCost: String) {
updateImportTicketController(with: .promptImport, ticketHolder: ticketHolder, ethCost: ethCost, dollarCost: dollarCost)
}
private func showImportSuccessful() {
updateImportTicketController(with: .succeeded)
}
private func showImportError(errorMessage: String) {
updateImportTicketController(with: .failed(errorMessage: errorMessage))
}
private func importUniversalLink(query: String, parameters: Parameters) {
updateImportTicketController(with: .processing)
Alamofire.request(
query,
@ -94,16 +129,15 @@ class UniversalLinkCoordinator: Coordinator {
}
}
if let vc = self.statusViewController {
if let vc = self.importTicketViewController {
// TODO handle http response
print(result)
if let vc = self.statusViewController, var viewModel = vc.viewModel {
if let vc = self.importTicketViewController, var viewModel = vc.viewModel {
if successful {
viewModel.state = .succeeded
vc.configure(viewModel: viewModel)
self.showImportSuccessful()
} else {
viewModel.state = .failed
vc.configure(viewModel: viewModel)
//TODO Pass in error message
self.showImportError(errorMessage: R.string.localizable.aClaimTicketFailedTitle())
}
}
}
@ -112,8 +146,15 @@ class UniversalLinkCoordinator: Coordinator {
}
extension UniversalLinkCoordinator: StatusViewControllerDelegate {
func didPressDone(in viewController: StatusViewController) {
extension UniversalLinkCoordinator: ImportTicketViewControllerDelegate {
func didPressDone(in viewController: ImportTicketViewController) {
viewController.dismiss(animated: true)
delegate?.completed(in: self)
}
func didPressImport(in viewController: ImportTicketViewController) {
if let query = viewController.query, let parameters = viewController.parameters {
importUniversalLink(query: query, parameters: parameters)
}
}
}

@ -0,0 +1,268 @@
// Copyright © 2018 Stormbird PTE. LTD. import Foundation
import UIKit
import Alamofire
protocol ImportTicketViewControllerDelegate: class {
func didPressDone(in viewController: ImportTicketViewController)
func didPressImport(in viewController: ImportTicketViewController)
}
class ImportTicketViewController: UIViewController {
weak var delegate: ImportTicketViewControllerDelegate?
//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 = TicketsViewControllerTitleHeader()
let ticketView = TicketRowView()
let statusLabel = UILabel()
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
let costStackView = UIStackView()
let ethCostLabelLabel = UILabel()
let ethCostLabel = UILabel()
let dollarCostLabelLabel = UILabel()
let dollarCostLabel = PaddedLabel()
let buttonSeparator = UIView()
let actionButton = UIButton(type: .system)
let cancelButton = UIButton(type: .system)
var viewModel: ImportTicketViewControllerViewModel?
var query: String?
var parameters: Parameters?
init() {
super.init(nibName: nil, bundle: nil)
view.backgroundColor = .clear
ticketView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(ticketView)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.hidesWhenStopped = true
roundedBackground.translatesAutoresizingMaskIntoConstraints = false
roundedBackground.backgroundColor = Colors.appWhite
roundedBackground.cornerRadius = 20
view.addSubview(roundedBackground)
ethCostLabelLabel.translatesAutoresizingMaskIntoConstraints = false
ethCostLabel.translatesAutoresizingMaskIntoConstraints = false
dollarCostLabelLabel.translatesAutoresizingMaskIntoConstraints = false
dollarCostLabel.translatesAutoresizingMaskIntoConstraints = false
let separator1 = UIView()
separator1.backgroundColor = UIColor(red: 230, green: 230, blue: 230)
let separator2 = UIView()
separator2.backgroundColor = UIColor(red: 230, green: 230, blue: 230)
costStackView.addArrangedSubview(ethCostLabelLabel)
costStackView.addArrangedSubview(.spacer(height: 7))
costStackView.addArrangedSubview(separator1)
costStackView.addArrangedSubview(.spacer(height: 7))
costStackView.addArrangedSubview(ethCostLabel)
costStackView.addArrangedSubview(.spacer(height: 7))
costStackView.addArrangedSubview(separator2)
costStackView.addArrangedSubview(.spacer(height: 7))
costStackView.addArrangedSubview(dollarCostLabelLabel)
costStackView.addArrangedSubview(.spacer(height: 3))
costStackView.addArrangedSubview(dollarCostLabel)
costStackView.translatesAutoresizingMaskIntoConstraints = false
costStackView.axis = .vertical
costStackView.spacing = 0
costStackView.distribution = .fill
costStackView.alignment = .center
actionButton.addTarget(self, action: #selector(actionTapped), for: .touchUpInside)
cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside)
let buttonsStackView = UIStackView(arrangedSubviews: [actionButton, cancelButton])
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
buttonsStackView.axis = .horizontal
buttonsStackView.spacing = 0
buttonsStackView.distribution = .fillEqually
buttonsStackView.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
let stackView = UIStackView(arrangedSubviews: [
header,
.spacer(height: 1),
ticketView,
.spacer(height: 1),
activityIndicator,
.spacer(height: 14),
statusLabel,
.spacer(height: 20),
costStackView,
])
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)
buttonSeparator.translatesAutoresizingMaskIntoConstraints = false
buttonSeparator.backgroundColor = Colors.appLightButtonSeparator
footerBar.addSubview(buttonSeparator)
let separatorThickness = CGFloat(1)
NSLayoutConstraint.activate([
header.heightAnchor.constraint(equalToConstant: 90),
ticketView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
ticketView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
separator1.heightAnchor.constraint(equalToConstant: 1),
separator1.leadingAnchor.constraint(equalTo: ticketView.background.leadingAnchor),
separator1.trailingAnchor.constraint(equalTo: ticketView.background.trailingAnchor),
separator2.heightAnchor.constraint(equalToConstant: 1),
separator2.leadingAnchor.constraint(equalTo: ticketView.background.leadingAnchor),
separator2.trailingAnchor.constraint(equalTo: ticketView.background.trailingAnchor),
buttonsStackView.leadingAnchor.constraint(equalTo: footerBar.leadingAnchor),
buttonsStackView.trailingAnchor.constraint(equalTo: footerBar.trailingAnchor),
buttonsStackView.topAnchor.constraint(equalTo: footerBar.topAnchor),
buttonsStackView.heightAnchor.constraint(equalToConstant: buttonsHeight),
buttonSeparator.leadingAnchor.constraint(equalTo: actionButton.trailingAnchor, constant: -separatorThickness / 2),
buttonSeparator.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: separatorThickness / 2),
buttonSeparator.topAnchor.constraint(equalTo: buttonsStackView.topAnchor, constant: 8),
buttonSeparator.bottomAnchor.constraint(equalTo: buttonsStackView.bottomAnchor, constant: -8),
footerBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
footerBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
footerBar.heightAnchor.constraint(equalToConstant: buttonsHeight),
footerBar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
statusLabel.widthAnchor.constraint(equalTo: ticketView.widthAnchor, constant: -20),
stackView.leadingAnchor.constraint(equalTo: roundedBackground.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: roundedBackground.trailingAnchor),
stackView.topAnchor.constraint(equalTo: roundedBackground.topAnchor),
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),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(viewModel: ImportTicketViewControllerViewModel) {
self.viewModel = viewModel
if let viewModel = self.viewModel {
view.backgroundColor = viewModel.backgroundColor
header.configure(title: viewModel.headerTitle)
ticketView.configure(viewModel: .init())
ticketView.isHidden = !viewModel.showTicketRow
ticketView.stateLabel.isHidden = true
ticketView.ticketCountLabel.text = viewModel.ticketCount
ticketView.titleLabel.text = viewModel.title
ticketView.venueLabel.text = viewModel.venue
ticketView.dateLabel.text = viewModel.date
ticketView.seatRangeLabel.text = viewModel.seatRange
ticketView.zoneNameLabel.text = viewModel.zoneName
ticketView.dateImageView.isHidden = !viewModel.showTicketRowIcons
ticketView.seatRangeImageView.isHidden = !viewModel.showTicketRowIcons
ticketView.zoneNameImageView.isHidden = !viewModel.showTicketRowIcons
statusLabel.textColor = viewModel.statusColor
statusLabel.font = viewModel.statusFont
statusLabel.textAlignment = .center
statusLabel.text = viewModel.statusText
statusLabel.numberOfLines = 0
costStackView.isHidden = !viewModel.showCost
ethCostLabelLabel.textColor = viewModel.ethCostLabelLabelColor
ethCostLabelLabel.font = viewModel.ethCostLabelLabelFont
ethCostLabelLabel.textAlignment = .center
ethCostLabelLabel.text = viewModel.ethCostLabelLabelText
ethCostLabel.textColor = viewModel.ethCostLabelColor
ethCostLabel.font = viewModel.ethCostLabelFont
ethCostLabel.textAlignment = .center
ethCostLabel.text = viewModel.ethCostLabelText
dollarCostLabelLabel.textColor = viewModel.dollarCostLabelLabelColor
dollarCostLabelLabel.font = viewModel.dollarCostLabelLabelFont
dollarCostLabelLabel.textAlignment = .center
dollarCostLabelLabel.text = viewModel.dollarCostLabelLabelText
dollarCostLabel.textColor = viewModel.dollarCostLabelColor
dollarCostLabel.font = viewModel.dollarCostLabelFont
dollarCostLabel.textAlignment = .center
dollarCostLabel.text = viewModel.dollarCostLabelText
dollarCostLabel.backgroundColor = viewModel.dollarCostLabelBackgroundColor
dollarCostLabel.layer.masksToBounds = true
dollarCostLabel.isHidden = !viewModel.showDollarCostLabel
activityIndicator.color = viewModel.activityIndicatorColor
if viewModel.showActivityIndicator {
activityIndicator.startAnimating()
} else {
activityIndicator.stopAnimating()
}
actionButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
actionButton.backgroundColor = viewModel.buttonBackgroundColor
actionButton.titleLabel?.font = viewModel.buttonFont
actionButton.setTitle(viewModel.actionButtonTitle, for: .normal)
cancelButton.setTitleColor(viewModel.buttonTitleColor, for: .normal)
cancelButton.backgroundColor = viewModel.buttonBackgroundColor
cancelButton.titleLabel?.font = viewModel.buttonFont
cancelButton.setTitle(viewModel.cancelButtonTitle, for: .normal)
actionButton.isHidden = !viewModel.showActionButton
buttonSeparator.isHidden = !viewModel.showActionButton
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//We can't use height / 2 because for some unknown reason, dollarCostLabel still has a zero height here
// dollarCostLabel.layer.cornerRadius = dollarCostLabel.frame.size.height / 2
dollarCostLabel.layer.cornerRadius = 18
}
@objc func actionTapped() {
delegate?.didPressImport(in: self)
}
@objc func cancel() {
if let delegate = delegate {
delegate.didPressDone(in: self)
} else {
dismiss(animated: true)
}
}
class PaddedLabel: UILabel {
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + 30, height: size.height + 10)
}
}
}

@ -0,0 +1,264 @@
// Copyright © 2018 Stormbird PTE. LTD.
import UIKit
struct ImportTicketViewControllerViewModel {
enum State {
case validating
case promptImport
case processing
case succeeded
case failed(errorMessage: String)
}
var state: State
var ticketHolder: TicketHolder?
var ethCost: String?
var dollarCost: String?
var backgroundColor: UIColor {
return Colors.appBackground
}
var contentsBackgroundColor: UIColor {
return Colors.appWhite
}
var headerTitle: String {
return R.string.localizable.aClaimTicketTitle()
}
var activityIndicatorColor: UIColor {
return Colors.appBackground
}
var showActivityIndicator: Bool {
switch state {
case .validating, .processing:
return true
case .promptImport, .succeeded, .failed:
return false
}
}
var showTicketRow: Bool {
switch state {
case .validating:
return false
case .processing, .promptImport, .succeeded, .failed:
return true
}
}
var ticketCount: String {
guard let ticketHolder = ticketHolder else { return "" }
if case let .validating = state {
return ""
} else {
return "x\(ticketHolder.tickets.count)"
}
}
var title: String {
guard let ticketHolder = ticketHolder else { return "" }
if case let .validating = state {
return ""
} else {
return ticketHolder.name
}
}
var seatRange: String {
guard let ticketHolder = ticketHolder else { return "" }
if case let .validating = state {
return ""
} else {
return ticketHolder.seatRange
}
}
var zoneName: String {
guard let ticketHolder = ticketHolder else { return "" }
if case let .validating = state {
return ""
} else {
return ticketHolder.zone
}
}
var venue: String {
guard let ticketHolder = ticketHolder else { return "" }
if case let .validating = state {
return ""
} else {
return ticketHolder.venue
}
}
var date: String {
guard let ticketHolder = ticketHolder else { return "" }
if case let .validating = state {
return ""
} else {
//TODO Should format be localized?
return ticketHolder.date.format("dd MMM yyyy")
}
}
var showTicketRowIcons: Bool {
if case let .validating = state {
return false
} else {
return true
}
}
var statusText: String {
switch state {
case .validating:
return R.string.localizable.aClaimTicketValidatingTitle()
case .promptImport:
return R.string.localizable.aClaimTicketPromptImportTitle()
case .processing:
return R.string.localizable.aClaimTicketInProgressTitle()
case .succeeded:
return R.string.localizable.aClaimTicketSuccessTitle()
case .failed:
return R.string.localizable.aClaimTicketFailedTitle()
}
}
var statusColor: UIColor {
return UIColor(red: 20, green: 20, blue: 20)
}
var statusFont: UIFont {
return Fonts.semibold(size: 25)!
}
var showCost: Bool {
return showTicketRow
}
var ethCostLabelLabelText: String {
return R.string.localizable.aClaimTicketEthCostLabelTitle()
}
var ethCostLabelLabelColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var ethCostLabelLabelFont: UIFont {
return Fonts.semibold(size: 21)!
}
var ethCostLabelText: String {
guard let ethCost = ethCost else { return R.string.localizable.aClaimTicketEthCostFreeTitle() }
if ethCost.isEmpty {
return R.string.localizable.aClaimTicketEthCostFreeTitle()
} else {
return "\(ethCost) ETH"
}
}
var ethCostLabelColor: UIColor {
return Colors.appBackground
}
var ethCostLabelFont: UIFont {
return Fonts.semibold(size: 21)!
}
var dollarCostLabelLabelText: String {
return R.string.localizable.aClaimTicketDollarCostLabelTitle()
}
var dollarCostLabelLabelColor: UIColor {
return UIColor(red: 155, green: 155, blue: 155)
}
var dollarCostLabelLabelFont: UIFont {
return Fonts.regular(size: 10)!
}
var dollarCostLabelText: String {
guard let dollarCost = dollarCost else { return "" }
if dollarCost.isEmpty {
return ""
} else {
return "$\(dollarCost)"
}
}
var showDollarCostLabel: Bool {
return !transactionIsFree
}
var dollarCostLabelBackgroundColor: UIColor {
return UIColor(red: 236, green: 236, blue: 236)
}
var dollarCostLabelColor: UIColor {
return Colors.darkGray
}
var dollarCostLabelFont: UIFont {
return Fonts.light(size: 21)!
}
var buttonTitleColor: UIColor {
return Colors.appWhite
}
var buttonBackgroundColor: UIColor {
return Colors.appHighlightGreen
}
var buttonFont: UIFont {
return Fonts.regular(size: 20)!
}
var showActionButton: Bool {
switch state {
case .promptImport:
return true
case .validating, .processing, .succeeded, .failed:
return false
}
}
var actionButtonTitle: String {
switch state {
case .validating:
return ""
case .promptImport:
if transactionIsFree {
return R.string.localizable.aClaimTicketImportButtonTitle()
} else {
return R.string.localizable.aClaimTicketPurchaseButtonTitle()
}
case .processing:
return ""
case .succeeded:
return ""
case .failed:
return ""
}
}
var cancelButtonTitle: String {
switch state {
case .validating, .promptImport, .processing:
return R.string.localizable.cancel()
case .succeeded, .failed:
return R.string.localizable.aClaimTicketDoneButtonTitle()
}
}
var transactionIsFree: Bool {
guard let ethCost = ethCost else { return true }
return ethCost.isEmpty
}
init(state: State) {
self.state = state
}
}
Loading…
Cancel
Save