SettingsViewController.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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. class SettingsViewController: FormViewController {
  13. struct cal {
  14. var title: String
  15. var identifier: String
  16. }
  17. override func viewDidLoad() {
  18. super.viewDidLoad()
  19. if UserDefaultsRepository.forceDarkMode.value {
  20. overrideUserInterfaceStyle = .dark
  21. }
  22. form
  23. +++ Section(header: "Nightscout Settings", footer: "Changing Nightscout settings requires an app restart.")
  24. <<< TextRow(){ row in
  25. row.title = "URL"
  26. row.placeholder = "https://mycgm.herokuapp.com"
  27. row.value = UserDefaultsRepository.url.value
  28. }.cellSetup { (cell, row) in
  29. cell.textField.autocorrectionType = .no
  30. }.onChange { row in
  31. guard let value = row.value else { return }
  32. // check the format of the URL entered by the user and trim away any spaces or "/" at the end
  33. var urlNSInput = value.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
  34. if urlNSInput.last == "/" {
  35. urlNSInput = String(urlNSInput.dropLast())
  36. }
  37. UserDefaultsRepository.url.value = urlNSInput.lowercased()
  38. // set the row value back to the correctly formatted URL so that the user immediately sees how it should have been written
  39. row.value = UserDefaultsRepository.url.value
  40. }
  41. <<< TextRow(){ row in
  42. row.title = "NS Token"
  43. row.placeholder = "Leave blank if not using tokens"
  44. row.value = UserDefaultsRepository.token.value
  45. }.cellSetup { (cell, row) in
  46. cell.textField.autocorrectionType = .no
  47. }.onChange { row in
  48. if row.value == nil {
  49. UserDefaultsRepository.token.value = ""
  50. }
  51. guard let value = row.value else { return }
  52. UserDefaultsRepository.token.value = value
  53. }
  54. <<< SegmentedRow<String>("units") { row in
  55. row.title = "Units"
  56. row.options = ["mg/dL", "mmol/L"]
  57. row.value = UserDefaultsRepository.units.value
  58. }.onChange { row in
  59. guard let value = row.value else { return }
  60. UserDefaultsRepository.units.value = value
  61. }
  62. buildGeneralSettings()
  63. buildAlarmSettings()
  64. buildGraphSettings()
  65. buildWatchSettings()
  66. buildDebugSettings()
  67. }
  68. func buildGeneralSettings() {
  69. form
  70. +++ Section("General Settings")
  71. <<< SwitchRow("colorBGText") { row in
  72. row.title = "Color Main BG Text"
  73. row.value = UserDefaultsRepository.colorBGText.value
  74. }.onChange { [weak self] row in
  75. guard let value = row.value else { return }
  76. UserDefaultsRepository.colorBGText.value = value
  77. // Force main screen update
  78. guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  79. mainScreen.setBGTextColor()
  80. }
  81. <<< SwitchRow("forceDarkMode") { row in
  82. row.title = "Force Dark Mode (Restart App)"
  83. row.value = UserDefaultsRepository.forceDarkMode.value
  84. }.onChange { [weak self] row in
  85. guard let value = row.value else { return }
  86. UserDefaultsRepository.forceDarkMode.value = value
  87. }
  88. <<< SwitchRow("persistentNotification") { row in
  89. row.title = "Persistent Notification"
  90. row.value = UserDefaultsRepository.persistentNotification.value
  91. }.onChange { [weak self] row in
  92. guard let value = row.value else { return }
  93. UserDefaultsRepository.persistentNotification.value = value
  94. }
  95. <<< SwitchRow("screenlockSwitchState") { row in
  96. row.title = "Keep Screen Active"
  97. row.value = UserDefaultsRepository.screenlockSwitchState.value
  98. }.onChange { [weak self] row in
  99. guard let value = row.value else { return }
  100. if value {
  101. UserDefaultsRepository.screenlockSwitchState.value = value
  102. }
  103. }
  104. <<< SwitchRow("backgroundRefresh"){ row in
  105. row.title = "Background Refresh"
  106. row.tag = "backgroundRefresh"
  107. row.value = UserDefaultsRepository.backgroundRefresh.value
  108. }.onChange { [weak self] row in
  109. guard let value = row.value else { return }
  110. UserDefaultsRepository.backgroundRefresh.value = value
  111. }
  112. <<< StepperRow("backgroundRefreshFrequency") { row in
  113. row.title = "Refresh Minutes"
  114. row.tag = "backgroundRefreshFrequency"
  115. row.cell.stepper.stepValue = 0.25
  116. row.cell.stepper.minimumValue = 0.25
  117. row.cell.stepper.maximumValue = 10
  118. row.value = Double(UserDefaultsRepository.backgroundRefreshFrequency.value)
  119. row.hidden = "$backgroundRefresh == false"
  120. }.onChange { [weak self] row in
  121. guard let value = row.value else { return }
  122. UserDefaultsRepository.backgroundRefreshFrequency.value = value
  123. }
  124. <<< SwitchRow("appBadge"){ row in
  125. row.title = "Display App Badge"
  126. row.tag = "appBadge"
  127. row.value = UserDefaultsRepository.appBadge.value
  128. }.onChange { [weak self] row in
  129. guard let value = row.value else { return }
  130. UserDefaultsRepository.appBadge.value = value
  131. // Force main screen update
  132. guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  133. mainScreen.nightscoutLoader(forceLoad: true)
  134. }
  135. <<< SwitchRow("speakBG"){ row in
  136. row.title = "Speak BG"
  137. row.value = UserDefaultsRepository.speakBG.value
  138. }.onChange { [weak self] row in
  139. guard let value = row.value else { return }
  140. UserDefaultsRepository.speakBG.value = value
  141. }
  142. }
  143. func buildAlarmSettings() {
  144. form
  145. +++ Section("Alarm Settings")
  146. <<< SwitchRow("overrideSystemOutputVolume"){ row in
  147. row.title = "Override System Volume"
  148. row.value = UserDefaultsRepository.overrideSystemOutputVolume.value
  149. }.onChange { [weak self] row in
  150. guard let value = row.value else { return }
  151. UserDefaultsRepository.overrideSystemOutputVolume.value = value
  152. }
  153. <<< StepperRow("forcedOutputVolume") { row in
  154. row.title = "Volume Level"
  155. row.cell.stepper.stepValue = 0.05
  156. row.cell.stepper.minimumValue = 0
  157. row.cell.stepper.maximumValue = 1
  158. row.value = Double(UserDefaultsRepository.forcedOutputVolume.value)
  159. row.hidden = "$overrideSystemOutputVolume == false"
  160. row.displayValueFor = { value in
  161. guard let value = value else { return nil }
  162. return "\(Int(value*100))%"
  163. }
  164. }.onChange { [weak self] row in
  165. guard let value = row.value else { return }
  166. UserDefaultsRepository.forcedOutputVolume.value = Float(value)
  167. }
  168. /* <<< StepperRow("fadeInTimeInterval") { row in
  169. row.title = "Fade-in Seconds"
  170. row.cell.stepper.stepValue = 5
  171. row.cell.stepper.minimumValue = 0
  172. row.cell.stepper.maximumValue = 60
  173. row.value = Double(UserDefaultsRepository.fadeInTimeInterval.value)
  174. row.displayValueFor = { value in
  175. guard let value = value else { return nil }
  176. return "\(Int(value))"
  177. }
  178. }.onChange { [weak self] row in
  179. guard let value = row.value else { return }
  180. UserDefaultsRepository.fadeInTimeInterval.value = TimeInterval(value)
  181. }*/
  182. }
  183. func buildGraphSettings() {
  184. form
  185. +++ Section("Graph Settings")
  186. <<< SwitchRow("militaryTime"){ row in
  187. row.title = "24 Hour Time"
  188. row.value = UserDefaultsRepository.militaryTime.value
  189. }.onChange { [weak self] row in
  190. guard let value = row.value else { return }
  191. UserDefaultsRepository.militaryTime.value = value
  192. // Force main screen update
  193. guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  194. mainScreen.updateBGGraphSettings()
  195. }
  196. <<< SwitchRow("switchRowDots"){ row in
  197. row.title = "Display Dots"
  198. row.value = UserDefaultsRepository.showDots.value
  199. }.onChange { [weak self] row in
  200. guard let value = row.value else { return }
  201. UserDefaultsRepository.showDots.value = value
  202. // Force main screen update
  203. guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  204. mainScreen.updateBGGraphSettings()
  205. }
  206. <<< SwitchRow("switchRowLines"){ row in
  207. row.title = "Display Lines"
  208. row.value = UserDefaultsRepository.showLines.value
  209. }.onChange { [weak self] row in
  210. guard let value = row.value else { return }
  211. UserDefaultsRepository.showLines.value = value
  212. // Force main screen update
  213. guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  214. mainScreen.updateBGGraphSettings()
  215. }
  216. <<< SwitchRow("offsetCarbsBolus"){ row in
  217. row.title = "Offset Carb/Bolus Dots"
  218. row.value = UserDefaultsRepository.offsetCarbsBolus.value
  219. }.onChange { [weak self] row in
  220. guard let value = row.value else { return }
  221. UserDefaultsRepository.offsetCarbsBolus.value = value
  222. }
  223. <<< StepperRow("minBGScale") { row in
  224. row.title = "Min BG Scale"
  225. row.cell.stepper.stepValue = 1
  226. row.cell.stepper.minimumValue = Double(UserDefaultsRepository.highLine.value)
  227. row.cell.stepper.maximumValue = 400
  228. row.value = Double(UserDefaultsRepository.minBGScale.value)
  229. row.displayValueFor = { value in
  230. guard let value = value else { return nil }
  231. return bgUnits.toDisplayUnits(String(value))
  232. }
  233. }.onChange { [weak self] row in
  234. guard let value = row.value else { return }
  235. UserDefaultsRepository.minBGScale.value = Float(value)
  236. }
  237. <<< StepperRow("minBasalScale") { row in
  238. row.title = "Min Basal Scale"
  239. row.cell.stepper.stepValue = 0.5
  240. row.cell.stepper.minimumValue = 0.5
  241. row.cell.stepper.maximumValue = 20
  242. row.value = Double(UserDefaultsRepository.minBasalScale.value)
  243. }.onChange { [weak self] row in
  244. guard let value = row.value else { return }
  245. UserDefaultsRepository.minBasalScale.value = value
  246. }
  247. <<< StepperRow("lowLine") { row in
  248. row.title = "Low BG Display Value"
  249. row.cell.stepper.stepValue = 1
  250. row.cell.stepper.minimumValue = 40
  251. row.cell.stepper.maximumValue = 120
  252. row.value = Double(UserDefaultsRepository.lowLine.value)
  253. row.displayValueFor = { value in
  254. guard let value = value else { return nil }
  255. return bgUnits.toDisplayUnits(String(value))
  256. }
  257. }.onChange { [weak self] row in
  258. guard let value = row.value else { return }
  259. UserDefaultsRepository.lowLine.value = Float(value)
  260. // Force main screen update
  261. guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  262. mainScreen.updateBGGraphSettings()
  263. }
  264. <<< StepperRow("highLine") { row in
  265. row.title = "High BG Display Value"
  266. row.cell.stepper.stepValue = 1
  267. row.cell.stepper.minimumValue = 120
  268. row.cell.stepper.maximumValue = 400
  269. row.value = Double(UserDefaultsRepository.highLine.value)
  270. row.displayValueFor = { value in
  271. guard let value = value else { return nil }
  272. return bgUnits.toDisplayUnits(String(value))
  273. }
  274. }.onChange { [weak self] row in
  275. guard let value = row.value else { return }
  276. UserDefaultsRepository.highLine.value = Float(value)
  277. // Force main screen update
  278. guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  279. mainScreen.updateBGGraphSettings()
  280. }
  281. }
  282. func buildWatchSettings(){
  283. //array of calendars
  284. let store = EKEventStore()
  285. let ekCalendars = store.calendars(for: EKEntityType.event)
  286. var calendars: [cal] = []
  287. for i in 0..<ekCalendars.count{
  288. let item = cal(title: ekCalendars[i].title, identifier: ekCalendars[i].calendarIdentifier)
  289. calendars.append(item)
  290. }
  291. form
  292. +++ Section(header: "Watch Settings", footer: "Add the Apple calendar complication to your watch face for BG, Trend, Delta, COB, and IOB updated every 5 minutes. Create a new calendar called 'Loop' and modify the calendar settings in the iPhone Watch App to only display the Loop calendar on your watch. It is important to use a new calendar because this will delete other events on the same calendar. Available variables are: %BG%, %DIRECTION%, %DELTA%, %MINAGO%, %IOB%, %COB%, %BASAL%, %LOOP%, and %OVERRIDE% (only displays the percentage). ** %MINAGO% only displays if it is an old reading")
  293. <<< SwitchRow("writeCalendarEvent"){ row in
  294. row.title = "BG to Calendar"
  295. row.value = UserDefaultsRepository.writeCalendarEvent.value
  296. }.onChange { [weak self] row in
  297. guard let value = row.value else { return }
  298. UserDefaultsRepository.writeCalendarEvent.value = value
  299. }
  300. <<< PickerInputRow<String>("calendarIdentifier") { row in
  301. row.title = "Calendar"
  302. row.options = calendars.map { $0.identifier }
  303. row.hidden = "$writeCalendarEvent == false"
  304. row.value = UserDefaultsRepository.calendarIdentifier.value
  305. row.displayValueFor = { value in
  306. guard let value = value else { return nil }
  307. let matching = calendars
  308. .flatMap { $0 }
  309. .filter { $0.identifier.range(of: value) != nil || $0.title.range(of: value) != nil }
  310. if matching.count > 0 {
  311. return "\(String(matching[0].title))"
  312. } else {
  313. return " - "
  314. }
  315. }
  316. }.onChange { [weak self] row in
  317. guard let value = row.value else { return }
  318. UserDefaultsRepository.calendarIdentifier.value = value
  319. }
  320. <<< TextRow("watchLine1"){ row in
  321. row.title = "Line 1"
  322. row.hidden = "$writeCalendarEvent == false"
  323. row.value = UserDefaultsRepository.watchLine1.value
  324. }.onChange { row in
  325. guard let value = row.value else { return }
  326. UserDefaultsRepository.watchLine1.value = value
  327. }
  328. <<< TextRow("watchLine2"){ row in
  329. row.title = "Line 2"
  330. row.hidden = "$writeCalendarEvent == false"
  331. row.value = UserDefaultsRepository.watchLine2.value
  332. }.onChange { row in
  333. guard let value = row.value else { return }
  334. UserDefaultsRepository.watchLine2.value = value
  335. }
  336. }
  337. func buildDebugSettings() {
  338. form
  339. +++ Section("Debug Settings")
  340. <<< SwitchRow("downloadBasal"){ row in
  341. row.title = "Download Basal"
  342. row.value = UserDefaultsRepository.downloadBasal.value
  343. }.onChange { [weak self] row in
  344. guard let value = row.value else { return }
  345. UserDefaultsRepository.downloadBasal.value = value
  346. }
  347. <<< SwitchRow("graphBasal"){ row in
  348. row.title = "Graph Basal"
  349. row.value = UserDefaultsRepository.graphBasal.value
  350. }.onChange { [weak self] row in
  351. guard let value = row.value else { return }
  352. UserDefaultsRepository.graphBasal.value = value
  353. }
  354. <<< SwitchRow("downloadBolus"){ row in
  355. row.title = "Download Bolus"
  356. row.value = UserDefaultsRepository.downloadBolus.value
  357. }.onChange { [weak self] row in
  358. guard let value = row.value else { return }
  359. UserDefaultsRepository.downloadBolus.value = value
  360. }
  361. <<< SwitchRow("graphBolus"){ row in
  362. row.title = "Graph Bolus"
  363. row.value = UserDefaultsRepository.graphBolus.value
  364. }.onChange { [weak self] row in
  365. guard let value = row.value else { return }
  366. UserDefaultsRepository.graphBolus.value = value
  367. }
  368. <<< SwitchRow("downloadCarbs"){ row in
  369. row.title = "Download Carbs"
  370. row.value = UserDefaultsRepository.downloadCarbs.value
  371. }.onChange { [weak self] row in
  372. guard let value = row.value else { return }
  373. UserDefaultsRepository.downloadCarbs.value = value
  374. }
  375. <<< SwitchRow("graphCarbs"){ row in
  376. row.title = "Graph Carbs"
  377. row.value = UserDefaultsRepository.graphCarbs.value
  378. }.onChange { [weak self] row in
  379. guard let value = row.value else { return }
  380. UserDefaultsRepository.graphCarbs.value = value
  381. }
  382. <<< SwitchRow("downloadPrediction"){ row in
  383. row.title = "Download Prediction"
  384. row.value = UserDefaultsRepository.downloadPrediction.value
  385. }.onChange { [weak self] row in
  386. guard let value = row.value else { return }
  387. UserDefaultsRepository.downloadPrediction.value = value
  388. }
  389. <<< SwitchRow("graphPrediction"){ row in
  390. row.title = "Graph Prediction"
  391. row.value = UserDefaultsRepository.graphPrediction.value
  392. }.onChange { [weak self] row in
  393. guard let value = row.value else { return }
  394. UserDefaultsRepository.graphPrediction.value = value
  395. }
  396. <<< SwitchRow("debugLog"){ row in
  397. row.title = "Show Debug Log"
  398. row.value = UserDefaultsRepository.debugLog.value
  399. }.onChange { [weak self] row in
  400. guard let value = row.value else { return }
  401. UserDefaultsRepository.debugLog.value = value
  402. }
  403. <<< StepperRow("viewRefreshDelay") { row in
  404. row.title = "View Refresh Delay"
  405. row.cell.stepper.stepValue = 1
  406. row.cell.stepper.minimumValue = 5
  407. row.cell.stepper.maximumValue = 30
  408. row.value = Double(UserDefaultsRepository.viewRefreshDelay.value)
  409. row.displayValueFor = { value in
  410. guard let value = value else { return nil }
  411. return "\(Int(value))"
  412. }
  413. }.onChange { [weak self] row in
  414. guard let value = row.value else { return }
  415. UserDefaultsRepository.viewRefreshDelay.value = Double(value)
  416. }
  417. }
  418. }