MockServiceTableViewController.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. //
  2. // MockServiceSettingsViewController.swift
  3. // MockKitUI
  4. //
  5. // Created by Darin Krauss on 5/17/19.
  6. // Copyright © 2019 LoopKit Authors. All rights reserved.
  7. //
  8. import UIKit
  9. import LoopKit
  10. import LoopKitUI
  11. import MockKit
  12. final class MockServiceTableViewController: UITableViewController {
  13. public enum Operation {
  14. case create
  15. case update
  16. }
  17. private let service: MockService
  18. private let operation: Operation
  19. init(service: MockService, for operation: Operation) {
  20. self.service = service
  21. self.operation = operation
  22. super.init(style: .grouped)
  23. }
  24. required init?(coder: NSCoder) {
  25. fatalError("init(coder:) has not been implemented")
  26. }
  27. override func viewDidLoad() {
  28. super.viewDidLoad()
  29. tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className)
  30. tableView.register(SwitchTableViewCell.self, forCellReuseIdentifier: SwitchTableViewCell.className)
  31. tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className)
  32. title = service.localizedTitle
  33. if operation == .create {
  34. navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancel))
  35. }
  36. navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done))
  37. }
  38. @objc private func cancel() {
  39. notifyComplete()
  40. }
  41. @objc private func done() {
  42. switch operation {
  43. case .create:
  44. service.completeCreate()
  45. if let serviceNavigationController = navigationController as? ServiceNavigationController {
  46. serviceNavigationController.notifyServiceCreatedAndOnboarded(service)
  47. }
  48. case .update:
  49. service.completeUpdate()
  50. }
  51. notifyComplete()
  52. }
  53. private func confirmDeletion(completion: (() -> Void)? = nil) {
  54. let alert = UIAlertController(serviceDeletionHandler: {
  55. self.service.completeDelete()
  56. self.notifyComplete()
  57. })
  58. present(alert, animated: true, completion: completion)
  59. }
  60. private func notifyComplete() {
  61. if let serviceNavigationController = navigationController as? ServiceNavigationController {
  62. serviceNavigationController.notifyComplete()
  63. }
  64. }
  65. // MARK: - Data Source
  66. private enum Section: Int, CaseIterable {
  67. case source
  68. case history
  69. case deleteService
  70. }
  71. private enum Source: Int, CaseIterable {
  72. case remoteData
  73. case logging
  74. case analytics
  75. }
  76. private enum History: Int, CaseIterable {
  77. case viewHistory
  78. case clearHistory
  79. }
  80. // MARK: - UITableViewDataSource
  81. override func numberOfSections(in tableView: UITableView) -> Int {
  82. switch operation {
  83. case .create:
  84. return Section.allCases.count - 2 // No history or deleteService
  85. case .update:
  86. return Section.allCases.count
  87. }
  88. }
  89. override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  90. switch Section(rawValue: section)! {
  91. case .source:
  92. return Source.allCases.count
  93. case .history:
  94. return History.allCases.count
  95. case .deleteService:
  96. return 1
  97. }
  98. }
  99. override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  100. switch Section(rawValue: section)! {
  101. case .source:
  102. return LocalizedString("Source", comment: "Caption for Source")
  103. case .history:
  104. return LocalizedString("History", comment: "Caption for History")
  105. case .deleteService:
  106. return " " // Use an empty string for more dramatic spacing
  107. }
  108. }
  109. override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
  110. switch Section(rawValue: indexPath.section)! {
  111. case .source:
  112. return false
  113. default:
  114. return true
  115. }
  116. }
  117. override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  118. switch Section(rawValue: indexPath.section)! {
  119. case .source:
  120. let cell = tableView.dequeueReusableCell(withIdentifier: SwitchTableViewCell.className, for: indexPath) as! SwitchTableViewCell
  121. switch Source(rawValue: indexPath.row)! {
  122. case .remoteData:
  123. cell.textLabel?.text = "Remote Data"
  124. cell.switch?.isOn = service.remoteData
  125. cell.switch?.addTarget(self, action: #selector(remoteDataChanged(_:)), for: .valueChanged)
  126. case .logging:
  127. cell.textLabel?.text = "Logging"
  128. cell.switch?.isOn = service.logging
  129. cell.switch?.addTarget(self, action: #selector(loggingChanged(_:)), for: .valueChanged)
  130. case .analytics:
  131. cell.textLabel?.text = "Analytics"
  132. cell.switch?.isOn = service.analytics
  133. cell.switch?.addTarget(self, action: #selector(analyticsChanged(_:)), for: .valueChanged)
  134. }
  135. return cell
  136. case .history:
  137. switch History(rawValue: indexPath.row)! {
  138. case .viewHistory:
  139. let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) as! SettingsTableViewCell
  140. cell.textLabel?.text = "View History"
  141. cell.accessoryType = .disclosureIndicator
  142. return cell
  143. case .clearHistory:
  144. let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell
  145. cell.textLabel?.text = "Clear History"
  146. cell.tintColor = .delete
  147. return cell
  148. }
  149. case .deleteService:
  150. let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell
  151. cell.textLabel?.text = "Delete Service"
  152. cell.textLabel?.textAlignment = .center
  153. cell.tintColor = .delete
  154. return cell
  155. }
  156. }
  157. @objc private func remoteDataChanged(_ sender: UISwitch) {
  158. service.remoteData = sender.isOn
  159. }
  160. @objc private func loggingChanged(_ sender: UISwitch) {
  161. service.logging = sender.isOn
  162. }
  163. @objc private func analyticsChanged(_ sender: UISwitch) {
  164. service.analytics = sender.isOn
  165. }
  166. // MARK: - UITableViewDelegate
  167. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  168. switch Section(rawValue: indexPath.section)! {
  169. case .source:
  170. break
  171. case .history:
  172. switch History(rawValue: indexPath.row)! {
  173. case .viewHistory:
  174. show(MockServiceHistoryViewController.init(mockService: service), sender: tableView.cellForRow(at: indexPath))
  175. case .clearHistory:
  176. let alert = UIAlertController(clearHistoryHandler: {
  177. self.service.clearHistory()
  178. })
  179. present(alert, animated: true) {
  180. tableView.deselectRow(at: indexPath, animated: true)
  181. }
  182. }
  183. case .deleteService:
  184. confirmDeletion {
  185. tableView.deselectRow(at: indexPath, animated: true)
  186. }
  187. }
  188. }
  189. }
  190. fileprivate class MockServiceHistoryViewController: UIViewController {
  191. private let mockService: MockService
  192. private lazy var textView = UITextView()
  193. init(mockService: MockService) {
  194. self.mockService = mockService
  195. super.init(nibName: nil, bundle: nil)
  196. }
  197. required init?(coder: NSCoder) {
  198. fatalError("init(coder:) has not been implemented")
  199. }
  200. override func loadView() {
  201. view = textView
  202. }
  203. override func viewDidLoad() {
  204. super.viewDidLoad()
  205. title = LocalizedString("History", comment: "History Caption")
  206. textView.contentInsetAdjustmentBehavior = .always
  207. textView.isEditable = false
  208. if let font = UIFont(name: "Menlo-Regular", size: 12) {
  209. textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: font)
  210. }
  211. textView.text = mockService.history.joined(separator: "\n")
  212. navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(share))
  213. }
  214. @objc func share() {
  215. let timestamp = ISO8601DateFormatter.string(from: Date(), timeZone: .current, formatOptions: [.withInternetDateTime])
  216. let title = "\(mockService.localizedTitle)_History_\(timestamp).txt"
  217. let item = SharedResponse(text: textView.text, title: title)
  218. do {
  219. try item.write()
  220. } catch let error {
  221. present(UIAlertController(with: error), animated: true)
  222. return
  223. }
  224. present(UIActivityViewController(activityItems: [item], applicationActivities: nil), animated: true)
  225. }
  226. }
  227. fileprivate class SharedResponse: NSObject, UIActivityItemSource {
  228. private let text: String
  229. private let title: String
  230. private let url: URL
  231. init(text: String, title: String) {
  232. var url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
  233. url.appendPathComponent(title, isDirectory: false)
  234. self.text = text
  235. self.title = title
  236. self.url = url
  237. super.init()
  238. }
  239. func write() throws {
  240. try text.write(to: url, atomically: true, encoding: .utf8)
  241. }
  242. // MARK: - UIActivityItemSource
  243. func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
  244. return url
  245. }
  246. func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
  247. return url
  248. }
  249. func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
  250. return title
  251. }
  252. func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String {
  253. return "public.utf8-plain-text"
  254. }
  255. }
  256. fileprivate extension UIAlertController {
  257. convenience init(clearHistoryHandler handler: @escaping () -> Void) {
  258. self.init(title: nil, message: "Are you sure you want to clear the history?", preferredStyle: .actionSheet)
  259. addAction(UIAlertAction(title: "Clear History", style: .destructive, handler: { _ in handler() }))
  260. addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
  261. }
  262. convenience init(serviceDeletionHandler handler: @escaping () -> Void) {
  263. self.init(
  264. title: nil,
  265. message: LocalizedString("Are you sure you want to delete this service?", comment: "Confirmation message for deleting a service"),
  266. preferredStyle: .actionSheet
  267. )
  268. addAction(UIAlertAction(
  269. title: LocalizedString("Delete Service", comment: "Button title to delete a service"),
  270. style: .destructive,
  271. handler: { _ in
  272. handler()
  273. }))
  274. let cancel = LocalizedString("Cancel", comment: "The title of the cancel action in an action sheet")
  275. addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil))
  276. }
  277. }