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