SettingsViewController.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. //
  2. // SettingsViewController.swift
  3. // LoopFollow
  4. //
  5. // Created by Jon Fawcett on 6/3/20.
  6. // Copyright © 2020 Jon Fawcett. All rights reserved.
  7. //
  8. import UIKit
  9. import Eureka
  10. import EventKit
  11. import EventKitUI
  12. import SwiftUI
  13. class SettingsViewController: FormViewController, NightscoutSettingsViewModelDelegate {
  14. var tokenRow: TextRow?
  15. var appStateController: AppStateController?
  16. var statusLabelRow: LabelRow!
  17. func showHideNSDetails() {
  18. var isHidden = false
  19. var isEnabled = true
  20. if !IsNightscoutEnabled() {
  21. isHidden = true
  22. isEnabled = false
  23. }
  24. if let row1 = form.rowBy(tag: "informationDisplaySettings") as? ButtonRow {
  25. row1.hidden = .function(["hide"], {form in
  26. return isHidden
  27. })
  28. row1.evaluateHidden()
  29. }
  30. if IsNightscoutEnabled() {
  31. isEnabled = true
  32. }
  33. guard let nightscoutTab = self.tabBarController?.tabBar.items![3] else { return }
  34. nightscoutTab.isEnabled = isEnabled
  35. }
  36. override func viewDidLoad() {
  37. super.viewDidLoad()
  38. if UserDefaultsRepository.forceDarkMode.value {
  39. overrideUserInterfaceStyle = .dark
  40. }
  41. let buildDetails = BuildDetails.default
  42. let formattedBuildDate = dateTimeUtils.formattedDate(from: buildDetails.buildDate())
  43. let branchAndSha = buildDetails.branchAndSha
  44. let expiration = dateTimeUtils.formattedDate(from: buildDetails.calculateExpirationDate())
  45. let expirationHeaderString = buildDetails.expirationHeaderString
  46. let versionManager = AppVersionManager()
  47. let version = versionManager.version()
  48. let isMacApp = buildDetails.isMacApp()
  49. let isSimulatorBuild = buildDetails.isSimulatorBuild()
  50. form
  51. +++ Section(header: "Data Settings", footer: "")
  52. <<< SegmentedRow<String>("units") { row in
  53. row.title = "Units"
  54. row.options = ["mg/dL", "mmol/L"]
  55. row.value = UserDefaultsRepository.units.value
  56. }.onChange { row in
  57. guard let value = row.value else { return }
  58. UserDefaultsRepository.units.value = value
  59. }
  60. <<< ButtonRow("nightscout") {
  61. $0.title = "Nightscout Settings"
  62. $0.presentationMode = .show(
  63. controllerProvider: .callback(builder: {
  64. self.presentNightscoutSettingsView()
  65. return UIViewController()
  66. }), onDismiss: nil
  67. )
  68. }
  69. <<< ButtonRow("dexcom") {
  70. $0.title = "Dexcom Settings"
  71. $0.presentationMode = .show(
  72. controllerProvider: .callback(builder: {
  73. self.presentDexcomSettingsView()
  74. return UIViewController()
  75. }), onDismiss: nil
  76. )
  77. }
  78. +++ Section("App Settings")
  79. <<< ButtonRow("backgroundRefreshSettings") {
  80. $0.title = "Background Refresh Settings"
  81. $0.presentationMode = .show(
  82. controllerProvider: .callback(builder: {
  83. self.presentBackgroundRefreshSettings()
  84. return UIViewController()
  85. }),
  86. onDismiss: nil
  87. )
  88. }
  89. <<< ButtonRow() {
  90. $0.title = "General Settings"
  91. $0.presentationMode = .show(
  92. controllerProvider: .callback(builder: {
  93. let controller = GeneralSettingsViewController()
  94. controller.appStateController = self.appStateController
  95. return controller
  96. }
  97. ), onDismiss: nil)
  98. }
  99. <<< ButtonRow("graphSettings") {
  100. $0.title = "Graph Settings"
  101. $0.presentationMode = .show(
  102. controllerProvider: .callback(builder: {
  103. let controller = GraphSettingsViewController()
  104. controller.appStateController = self.appStateController
  105. return controller
  106. }
  107. ), onDismiss: nil)
  108. }
  109. <<< ButtonRow("informationDisplaySettings") {
  110. $0.title = "Information Display Settings"
  111. $0.presentationMode = .show(
  112. controllerProvider: .callback(builder: {
  113. self.presentInfoDisplaySettings()
  114. return UIViewController()
  115. }
  116. ), onDismiss: nil)
  117. }
  118. <<< ButtonRow("alarmsSettings") {
  119. $0.title = "Alarms"
  120. $0.presentationMode = .show(
  121. controllerProvider: .callback(builder: {
  122. guard let alarmVC = ViewControllerManager.shared.alarmViewController else {
  123. fatalError("AlarmViewController should be pre-instantiated and available")
  124. }
  125. return alarmVC
  126. }), onDismiss: nil)
  127. }
  128. <<< ButtonRow("remoteSettings") {
  129. $0.title = "Remote Settings"
  130. $0.presentationMode = .show(
  131. controllerProvider: .callback(builder: {
  132. self.presentRemoteSettings()
  133. return UIViewController()
  134. }),
  135. onDismiss: nil
  136. )
  137. }
  138. +++ Section("Integrations")
  139. <<< ButtonRow() {
  140. $0.title = "Calendar"
  141. $0.presentationMode = .show(
  142. controllerProvider: .callback(builder: {
  143. let controller = WatchSettingsViewController()
  144. controller.appStateController = self.appStateController
  145. return controller
  146. }
  147. ), onDismiss: nil)
  148. }
  149. <<< ButtonRow("contact") {
  150. $0.title = "Contact"
  151. $0.presentationMode = .show(
  152. controllerProvider: .callback(builder: {
  153. self.presentContactSettings()
  154. return UIViewController()
  155. }
  156. ), onDismiss: nil)
  157. }
  158. +++ Section("Advanced Settings")
  159. <<< ButtonRow() {
  160. $0.title = "Advanced Settings"
  161. $0.presentationMode = .show(
  162. controllerProvider: .callback(builder: {
  163. self.presentAdvancedSettingsView()
  164. return UIViewController()
  165. }), onDismiss: nil)
  166. }
  167. +++ Section("Logging")
  168. <<< ButtonRow("viewlog") {
  169. $0.title = "View Log"
  170. $0.presentationMode = .show(
  171. controllerProvider: .callback(builder: {
  172. self.presentLogView()
  173. return UIViewController()
  174. }), onDismiss: nil)
  175. }
  176. <<< ButtonRow("shareLogs") {
  177. $0.title = "Share Logs"
  178. $0.cellSetup { cell, row in
  179. cell.accessibilityIdentifier = "ShareLogsButton"
  180. }
  181. $0.onCellSelection { [weak self] _, _ in
  182. self?.shareLogs()
  183. }
  184. }
  185. +++ Section("Build Information")
  186. <<< LabelRow() {
  187. $0.title = "Version"
  188. $0.value = version
  189. $0.tag = "currentVersionRow"
  190. }
  191. <<< LabelRow() {
  192. $0.title = "Latest version"
  193. $0.value = "Fetching..."
  194. $0.tag = "latestVersionRow"
  195. }
  196. <<< LabelRow() {
  197. $0.title = expirationHeaderString
  198. $0.value = expiration
  199. $0.hidden = Condition(booleanLiteral: isMacApp || isSimulatorBuild)
  200. }
  201. <<< LabelRow() {
  202. $0.title = "Built"
  203. $0.value = formattedBuildDate
  204. }
  205. <<< LabelRow() {
  206. $0.title = "Branch"
  207. $0.value = branchAndSha
  208. }
  209. showHideNSDetails()
  210. }
  211. override func viewWillAppear(_ animated: Bool) {
  212. super.viewWillAppear(animated)
  213. refreshVersionInfo()
  214. }
  215. func refreshVersionInfo() {
  216. let versionManager = AppVersionManager()
  217. versionManager.checkForNewVersion { latestVersion, isNewer, isBlacklisted in
  218. DispatchQueue.main.async {
  219. if let currentVersionRow = self.form.rowBy(tag: "currentVersionRow") as? LabelRow {
  220. currentVersionRow.cell.detailTextLabel?.textColor = self.getColor(isBlacklisted: isBlacklisted, isNewer: isNewer, isCurrent: latestVersion == versionManager.version())
  221. currentVersionRow.updateCell()
  222. }
  223. if let latestVersionRow = self.form.rowBy(tag: "latestVersionRow") as? LabelRow {
  224. latestVersionRow.value = latestVersion ?? "Unknown"
  225. latestVersionRow.updateCell()
  226. }
  227. }
  228. }
  229. }
  230. private func getColor(isBlacklisted: Bool, isNewer: Bool, isCurrent: Bool) -> UIColor {
  231. if isBlacklisted {
  232. return .red
  233. } else if isNewer {
  234. return .orange
  235. } else if isCurrent {
  236. return .green
  237. } else {
  238. return .secondaryLabel
  239. }
  240. }
  241. func presentInfoDisplaySettings() {
  242. let viewModel = InfoDisplaySettingsViewModel()
  243. let settingsView = InfoDisplaySettingsView(viewModel: viewModel)
  244. let hostingController = UIHostingController(rootView: settingsView)
  245. hostingController.modalPresentationStyle = .formSheet
  246. if UserDefaultsRepository.forceDarkMode.value {
  247. hostingController.overrideUserInterfaceStyle = .dark
  248. }
  249. present(hostingController, animated: true, completion: nil)
  250. }
  251. func presentRemoteSettings() {
  252. let viewModel = RemoteSettingsViewModel()
  253. let settingsView = RemoteSettingsView(viewModel: viewModel)
  254. let hostingController = UIHostingController(rootView: settingsView)
  255. hostingController.modalPresentationStyle = .formSheet
  256. if UserDefaultsRepository.forceDarkMode.value {
  257. hostingController.overrideUserInterfaceStyle = .dark
  258. }
  259. present(hostingController, animated: true, completion: nil)
  260. }
  261. func presentContactSettings() {
  262. let viewModel = ContactSettingsViewModel()
  263. let contactSettingsView = ContactSettingsView(viewModel: viewModel)
  264. let hostingController = UIHostingController(rootView: contactSettingsView)
  265. hostingController.modalPresentationStyle = .formSheet
  266. if UserDefaultsRepository.forceDarkMode.value {
  267. hostingController.overrideUserInterfaceStyle = .dark
  268. }
  269. present(hostingController, animated: true, completion: nil)
  270. }
  271. func presentBackgroundRefreshSettings() {
  272. let viewModel = BackgroundRefreshSettingsViewModel()
  273. let settingsView = BackgroundRefreshSettingsView(viewModel: viewModel)
  274. let hostingController = UIHostingController(rootView: settingsView)
  275. hostingController.modalPresentationStyle = .formSheet
  276. if UserDefaultsRepository.forceDarkMode.value {
  277. hostingController.overrideUserInterfaceStyle = .dark
  278. }
  279. present(hostingController, animated: true, completion: nil)
  280. }
  281. func presentLogView() {
  282. let viewModel = LogViewModel()
  283. let logView = LogView(viewModel: viewModel)
  284. let hostingController = UIHostingController(rootView: logView)
  285. hostingController.modalPresentationStyle = .formSheet
  286. if UserDefaultsRepository.forceDarkMode.value {
  287. hostingController.overrideUserInterfaceStyle = .dark
  288. }
  289. present(hostingController, animated: true, completion: nil)
  290. }
  291. func presentNightscoutSettingsView() {
  292. let viewModel = NightscoutSettingsViewModel()
  293. viewModel.delegate = self
  294. let view = NightscoutSettingsView(viewModel: viewModel)
  295. let hostingController = UIHostingController(rootView: view)
  296. hostingController.modalPresentationStyle = .formSheet
  297. if UserDefaultsRepository.forceDarkMode.value {
  298. hostingController.overrideUserInterfaceStyle = .dark
  299. }
  300. present(hostingController, animated: true, completion: nil)
  301. }
  302. func nightscoutSettingsDidFinish() {
  303. showHideNSDetails()
  304. }
  305. func presentDexcomSettingsView() {
  306. let viewModel = DexcomSettingsViewModel()
  307. let settingsView = DexcomSettingsView(viewModel: viewModel)
  308. let hostingController = UIHostingController(rootView: settingsView)
  309. hostingController.modalPresentationStyle = .formSheet
  310. if UserDefaultsRepository.forceDarkMode.value {
  311. hostingController.overrideUserInterfaceStyle = .dark
  312. }
  313. present(hostingController, animated: true, completion: nil)
  314. }
  315. func presentAdvancedSettingsView() {
  316. let viewModel = AdvancedSettingsViewModel()
  317. let view = AdvancedSettingsView(viewModel: viewModel)
  318. let hostingController = UIHostingController(rootView: view)
  319. hostingController.modalPresentationStyle = .formSheet
  320. if UserDefaultsRepository.forceDarkMode.value {
  321. hostingController.overrideUserInterfaceStyle = .dark
  322. }
  323. present(hostingController, animated: true, completion: nil)
  324. }
  325. private func shareLogs() {
  326. let logFilesToShare = LogManager.shared.logFilesForTodayAndYesterday()
  327. if !logFilesToShare.isEmpty {
  328. let activityViewController = UIActivityViewController(activityItems: logFilesToShare, applicationActivities: nil)
  329. activityViewController.popoverPresentationController?.sourceView = self.view
  330. present(activityViewController, animated: true, completion: nil)
  331. } else {
  332. let alert = UIAlertController(title: "No Logs Available", message: "There are no logs to share.", preferredStyle: .alert)
  333. alert.addAction(UIAlertAction(title: "OK", style: .default))
  334. present(alert, animated: true, completion: nil)
  335. }
  336. }
  337. }