AuthenticationViewController.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. //
  2. // AuthenticationViewController.swift
  3. // Loop
  4. //
  5. // Created by Nate Racklyeft on 7/2/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import UIKit
  9. class AuthenticationViewController<T: ServiceAuthentication>: UITableViewController, IdentifiableClass, UITextFieldDelegate {
  10. typealias AuthenticationObserver = (authentication: T) -> Void
  11. var authenticationObserver: AuthenticationObserver?
  12. var authentication: T
  13. private var state: AuthenticationState = .Empty {
  14. didSet {
  15. switch (oldValue, state) {
  16. case let (x, y) where x == y:
  17. break
  18. case (_, .Verifying):
  19. let titleView = ValidatingIndicatorView(frame: CGRect.zero)
  20. UIView.animateWithDuration(0.25) {
  21. self.navigationItem.hidesBackButton = true
  22. self.navigationItem.titleView = titleView
  23. }
  24. tableView.reloadSections(NSIndexSet(indexesInRange: NSRange(0...1)), withRowAnimation: .Automatic)
  25. authentication.verify { [unowned self] (success, error) in
  26. dispatch_async(dispatch_get_main_queue()) {
  27. UIView.animateWithDuration(0.25) {
  28. self.navigationItem.titleView = nil
  29. self.navigationItem.hidesBackButton = false
  30. }
  31. if success {
  32. self.state = .Authorized
  33. } else {
  34. if let error = error {
  35. self.presentAlertControllerWithError(error)
  36. }
  37. self.state = .Unauthorized
  38. }
  39. }
  40. }
  41. case (_, .Authorized), (_, .Unauthorized):
  42. authenticationObserver?(authentication: authentication)
  43. tableView.reloadSections(NSIndexSet(indexesInRange: NSRange(0...1)), withRowAnimation: .Automatic)
  44. default:
  45. break
  46. }
  47. }
  48. }
  49. init(authentication: T) {
  50. self.authentication = authentication
  51. state = authentication.isAuthorized ? .Authorized : .Unauthorized
  52. super.init(style: .Grouped)
  53. title = authentication.title
  54. }
  55. override func viewDidLoad() {
  56. super.viewDidLoad()
  57. tableView.registerNib(AuthenticationTableViewCell.nib(), forCellReuseIdentifier: AuthenticationTableViewCell.className)
  58. tableView.registerNib(ButtonTableViewCell.nib(), forCellReuseIdentifier: ButtonTableViewCell.className)
  59. }
  60. // MARK: - Table view data source
  61. override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
  62. return Section.count
  63. }
  64. override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  65. switch Section(rawValue: section)! {
  66. case .Credentials:
  67. switch state {
  68. case .Authorized:
  69. return authentication.credentials.filter({ !$0.isSecret }).count
  70. default:
  71. return authentication.credentials.count
  72. }
  73. case .Button:
  74. return 1
  75. }
  76. }
  77. override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  78. switch Section(rawValue: indexPath.section)! {
  79. case .Button:
  80. let cell = tableView.dequeueReusableCellWithIdentifier(ButtonTableViewCell.className, forIndexPath: indexPath) as! ButtonTableViewCell
  81. switch state {
  82. case .Authorized:
  83. cell.button.setTitle(NSLocalizedString("Delete Account", comment: "The title of the button to remove the credentials for a service"), forState: .Normal)
  84. cell.button.setTitleColor(UIColor.deleteColor, forState: .Normal)
  85. case .Empty, .Unauthorized, .Verifying:
  86. cell.button.setTitle(NSLocalizedString("Add Account", comment: "The title of the button to add the credentials for a service"), forState: .Normal)
  87. cell.button.setTitleColor(nil, forState: .Normal)
  88. }
  89. if case .Verifying = state {
  90. cell.button.enabled = false
  91. } else {
  92. cell.button.enabled = true
  93. }
  94. cell.button.addTarget(self, action: #selector(buttonPressed(_:)), forControlEvents: .TouchUpInside)
  95. return cell
  96. case .Credentials:
  97. let cell = tableView.dequeueReusableCellWithIdentifier(AuthenticationTableViewCell.className, forIndexPath: indexPath) as! AuthenticationTableViewCell
  98. let credential = authentication.credentials[indexPath.row]
  99. cell.titleLabel.text = credential.title
  100. cell.textField.tag = indexPath.row
  101. cell.textField.keyboardType = credential.keyboardType
  102. cell.textField.secureTextEntry = credential.isSecret
  103. cell.textField.returnKeyType = (indexPath.row < authentication.credentials.count - 1) ? .Next : .Done
  104. cell.textField.text = credential.value
  105. cell.textField.placeholder = credential.placeholder ?? NSLocalizedString("Required", comment: "The default placeholder string for a credential")
  106. cell.textField.delegate = self
  107. switch state {
  108. case .Authorized, .Verifying, .Empty:
  109. cell.textField.enabled = false
  110. case .Unauthorized:
  111. cell.textField.enabled = true
  112. }
  113. return cell
  114. }
  115. }
  116. private func validate() {
  117. state = .Verifying
  118. }
  119. // MARK: - Actions
  120. @objc private func buttonPressed(_: AnyObject) {
  121. tableView.endEditing(false)
  122. switch state {
  123. case .Authorized:
  124. authentication.reset()
  125. state = .Unauthorized
  126. case .Unauthorized:
  127. validate()
  128. default:
  129. break
  130. }
  131. }
  132. // MARK: - UITextFieldDelegate
  133. func textFieldDidEndEditing(textField: UITextField) {
  134. authentication.credentials[textField.tag].value = textField.text
  135. }
  136. func textFieldShouldReturn(textField: UITextField) -> Bool {
  137. if textField.returnKeyType == .Done {
  138. textField.resignFirstResponder()
  139. } else {
  140. let point = tableView.convertPoint(textField.frame.origin, fromView: textField.superview)
  141. if let indexPath = tableView.indexPathForRowAtPoint(point),
  142. cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: indexPath.row + 1, inSection: indexPath.section)) as? AuthenticationTableViewCell
  143. {
  144. cell.textField.becomeFirstResponder()
  145. validate()
  146. }
  147. }
  148. return true
  149. }
  150. }
  151. private enum Section: Int {
  152. case Credentials
  153. case Button
  154. static let count = 2
  155. }
  156. enum AuthenticationState {
  157. case Empty
  158. case Authorized
  159. case Verifying
  160. case Unauthorized
  161. }