Merge pull request #133 from James-Sangalli/add-import-ticket-from-universal-link-ui
Add UI for import ticket from universal link: prompt, success, failurepull/139/head
commit
8c76de5802
@ -0,0 +1,102 @@ |
||||
// Copyright © 2018 Stormbird PTE. LTD. |
||||
|
||||
import Foundation |
||||
import Alamofire |
||||
|
||||
protocol UniversalLinkCoordinatorDelegate: class { |
||||
func viewControllerForPresenting(in coordinator: UniversalLinkCoordinator) -> UIViewController? |
||||
} |
||||
|
||||
class UniversalLinkCoordinator: Coordinator { |
||||
var coordinators: [Coordinator] = [] |
||||
weak var delegate: UniversalLinkCoordinatorDelegate? |
||||
var statusViewController: TicketImportStatusViewController? |
||||
|
||||
func start() { |
||||
} |
||||
|
||||
//Returns true if handled |
||||
func handleUniversalLink(url: URL?) -> Bool { |
||||
let matchedPrefix = (url?.description.contains(UniversalLinkHandler().urlPrefix))! |
||||
guard matchedPrefix else { |
||||
return false |
||||
} |
||||
|
||||
let keystore = try! EtherKeystore() |
||||
let signedOrder = UniversalLinkHandler().parseURL(url: (url?.description)!) |
||||
let signature = signedOrder.signature.substring(from: 2) |
||||
|
||||
// form the json string out of the order for the paymaster server |
||||
// James S. wrote |
||||
let indices = signedOrder.order.indices |
||||
var indicesStringEncoded = "" |
||||
|
||||
for i in 0...indices.count - 1 { |
||||
indicesStringEncoded += String(indices[i]) + "," |
||||
} |
||||
//cut off last comma |
||||
indicesStringEncoded = indicesStringEncoded.substring(from: indicesStringEncoded.count - 1) |
||||
|
||||
let parameters: Parameters = [ |
||||
"address": keystore.recentlyUsedWallet?.address.description, |
||||
"indices": indicesStringEncoded, |
||||
"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 |
||||
|
||||
//TODO check if URL is valid or not. Price? |
||||
let validURL = true |
||||
if validURL { |
||||
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) |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
return true |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
private func importUniversalLink(query: String, parameters: Parameters) { |
||||
if let viewController = delegate?.viewControllerForPresenting(in: self) { |
||||
statusViewController = TicketImportStatusViewController() |
||||
if let vc = statusViewController { |
||||
vc.delegate = self |
||||
vc.configure(viewModel: .init(state: .processing)) |
||||
vc.modalPresentationStyle = .overCurrentContext |
||||
viewController.present(vc, animated: true) |
||||
} |
||||
} |
||||
|
||||
Alamofire.request( |
||||
query, |
||||
method: .post, |
||||
parameters: parameters |
||||
).responseJSON { |
||||
result in |
||||
// TODO handle http response |
||||
print(result) |
||||
//TODO handle successful or not. Pass an error (message?) to the view model if we have one |
||||
let successful = true |
||||
if let vc = self.statusViewController { |
||||
if successful { |
||||
vc.configure(viewModel: .init(state: .succeeded)) |
||||
} else { |
||||
vc.configure(viewModel: .init(state: .failed)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
extension UniversalLinkCoordinator: TicketImportStatusViewControllerDelegate { |
||||
func didPressDone(in viewController: TicketImportStatusViewController) { |
||||
viewController.dismiss(animated: true) |
||||
} |
||||
} |
@ -0,0 +1,129 @@ |
||||
// Copyright © 2018 Stormbird PTE. LTD. |
||||
|
||||
import Foundation |
||||
import UIKit |
||||
|
||||
protocol TicketImportStatusViewControllerDelegate: class { |
||||
func didPressDone(in viewController: TicketImportStatusViewController) |
||||
} |
||||
|
||||
class TicketImportStatusViewController: UIViewController { |
||||
weak var delegate: TicketImportStatusViewControllerDelegate? |
||||
let background = UIView() |
||||
let imageView = UIImageView() |
||||
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) |
||||
let titleLabel = UILabel() |
||||
let actionButton = UIButton() |
||||
|
||||
init() { |
||||
super.init(nibName: nil, bundle: nil) |
||||
view.backgroundColor = .clear |
||||
|
||||
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) |
||||
visualEffectView.translatesAutoresizingMaskIntoConstraints = false |
||||
view.insertSubview(visualEffectView, at: 0) |
||||
|
||||
let imageHolder = UIView() |
||||
|
||||
activityIndicator.translatesAutoresizingMaskIntoConstraints = false |
||||
activityIndicator.hidesWhenStopped = true |
||||
imageHolder.addSubview(activityIndicator) |
||||
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false |
||||
imageView.isHidden = true |
||||
imageHolder.addSubview(imageView) |
||||
|
||||
view.addSubview(background) |
||||
background.translatesAutoresizingMaskIntoConstraints = false |
||||
|
||||
actionButton.addTarget(self, action: #selector(done), for: .touchUpInside) |
||||
|
||||
let stackView = UIStackView(arrangedSubviews: [ |
||||
.spacer(height: 20), |
||||
imageHolder, |
||||
.spacer(height: 20), |
||||
titleLabel, |
||||
.spacer(height: 20), |
||||
actionButton, |
||||
.spacer(height: 18) |
||||
]) |
||||
stackView.translatesAutoresizingMaskIntoConstraints = false |
||||
stackView.axis = .vertical |
||||
stackView.spacing = 0 |
||||
stackView.distribution = .fill |
||||
background.addSubview(stackView) |
||||
|
||||
NSLayoutConstraint.activate([ |
||||
visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor), |
||||
visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor), |
||||
visualEffectView.topAnchor.constraint(equalTo: view.topAnchor), |
||||
visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor), |
||||
|
||||
imageView.widthAnchor.constraint(equalToConstant: 70), |
||||
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor), |
||||
imageView.centerXAnchor.constraint(equalTo: imageHolder.centerXAnchor), |
||||
imageView.centerYAnchor.constraint(equalTo: imageHolder.centerYAnchor), |
||||
|
||||
activityIndicator.centerXAnchor.constraint(equalTo: imageHolder.centerXAnchor), |
||||
activityIndicator.centerYAnchor.constraint(equalTo: imageHolder.centerYAnchor), |
||||
|
||||
imageHolder.heightAnchor.constraint(equalTo: imageView.heightAnchor), |
||||
|
||||
actionButton.heightAnchor.constraint(equalToConstant: 47), |
||||
|
||||
stackView.leadingAnchor.constraint(equalTo: background.leadingAnchor, constant: 40), |
||||
stackView.trailingAnchor.constraint(equalTo: background.trailingAnchor, constant: -40), |
||||
stackView.topAnchor.constraint(equalTo: background.topAnchor, constant: 16), |
||||
stackView.bottomAnchor.constraint(equalTo: background.bottomAnchor, constant: -16), |
||||
|
||||
background.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 42), |
||||
background.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -42), |
||||
background.centerYAnchor.constraint(equalTo: view.centerYAnchor) |
||||
]) |
||||
} |
||||
|
||||
required init?(coder aDecoder: NSCoder) { |
||||
fatalError("init(coder:) has not been implemented") |
||||
} |
||||
|
||||
func configure(viewModel: TicketImportStatusViewControllerViewModel) { |
||||
background.backgroundColor = viewModel.contentsBackgroundColor |
||||
background.layer.cornerRadius = 20 |
||||
|
||||
activityIndicator.color = viewModel.activityIndicatorColor |
||||
|
||||
if viewModel.showActivityIndicator { |
||||
activityIndicator.startAnimating() |
||||
} else { |
||||
activityIndicator.stopAnimating() |
||||
} |
||||
|
||||
imageView.isHidden = viewModel.showActivityIndicator |
||||
imageView.image = viewModel.image |
||||
|
||||
titleLabel.numberOfLines = 0 |
||||
titleLabel.textColor = viewModel.titleColor |
||||
titleLabel.font = viewModel.titleFont |
||||
titleLabel.textAlignment = .center |
||||
titleLabel.text = viewModel.titleLabelText |
||||
|
||||
actionButton.setTitleColor(viewModel.actionButtonTitleColor, for: .normal) |
||||
actionButton.setBackgroundColor(viewModel.actionButtonBackgroundColor, forState: .normal) |
||||
actionButton.titleLabel?.font = viewModel.actionButtonTitleFont |
||||
actionButton.setTitle(viewModel.actionButtonTitle, for: .normal) |
||||
actionButton.layer.masksToBounds = true |
||||
} |
||||
|
||||
override func viewDidLayoutSubviews() { |
||||
super.viewDidLayoutSubviews() |
||||
actionButton.layer.cornerRadius = actionButton.frame.size.height / 2 |
||||
} |
||||
|
||||
@objc func done() { |
||||
if let delegate = delegate { |
||||
delegate.didPressDone(in: self) |
||||
} else { |
||||
dismiss(animated: true) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,65 @@ |
||||
// Copyright © 2018 Stormbird PTE. LTD. |
||||
|
||||
import UIKit |
||||
|
||||
struct TicketImportStatusViewControllerViewModel { |
||||
enum State { |
||||
case processing |
||||
case succeeded |
||||
case failed |
||||
} |
||||
let state: State |
||||
|
||||
var contentsBackgroundColor: UIColor { |
||||
return Colors.appWhite |
||||
} |
||||
var image: UIImage? { |
||||
switch state { |
||||
case .processing: |
||||
return nil |
||||
case .succeeded: |
||||
return R.image.onboarding_complete() |
||||
case .failed: |
||||
//TODO return a failed version |
||||
return R.image.onboarding_complete() |
||||
} |
||||
} |
||||
var titleColor: UIColor { |
||||
return Colors.appText |
||||
} |
||||
var titleFont: UIFont { |
||||
return Fonts.light(size: 25)! |
||||
} |
||||
var activityIndicatorColor: UIColor { |
||||
return Colors.appBackground |
||||
} |
||||
var actionButtonTitleColor: UIColor { |
||||
return Colors.appWhite |
||||
} |
||||
var actionButtonBackgroundColor: UIColor { |
||||
return Colors.appBackground |
||||
} |
||||
var actionButtonTitleFont: UIFont { |
||||
return Fonts.regular(size: 20)! |
||||
} |
||||
var titleLabelText: String { |
||||
switch state { |
||||
case .processing: |
||||
return R.string.localizable.aClaimTicketInProgressTitle() |
||||
case .succeeded: |
||||
return R.string.localizable.aClaimTicketSuccessTitle() |
||||
case .failed: |
||||
return R.string.localizable.aClaimTicketFailedTitle() |
||||
} |
||||
} |
||||
var actionButtonTitle: String { |
||||
return R.string.localizable.aClaimTicketDoneButtonTitle() |
||||
} |
||||
var showActivityIndicator: Bool { |
||||
return state == .processing |
||||
} |
||||
|
||||
init(state: State) { |
||||
self.state = state |
||||
} |
||||
} |
Loading…
Reference in new issue