MockServiceTableViewController.swift 12 KB

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