SettingsViewController.swift 14 KB

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