GeneralSettingsViewController.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. //
  2. // GeneralSetingsViewController.swift
  3. // LoopFollow
  4. //
  5. // Created by Jose Paredes on 7/16/20.
  6. // Copyright © 2020 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. import Eureka
  10. import EventKit
  11. import EventKitUI
  12. class GeneralSettingsViewController: FormViewController {
  13. var appStateController: AppStateController?
  14. override func viewDidLoad() {
  15. super.viewDidLoad()
  16. if UserDefaultsRepository.forceDarkMode.value {
  17. overrideUserInterfaceStyle = .dark
  18. }
  19. buildGeneralSettings()
  20. // Register the GeneralSettingsViewController as an observer for the UIApplication.willEnterForegroundNotification, which will be triggered when the app enters the foreground. This helps ensure that the "Speak BG" switch in the General Settings is updated according to the current setting.
  21. NotificationCenter.default.addObserver(self, selector: #selector(handleAppWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  22. }
  23. private func buildGeneralSettings() {
  24. form
  25. +++ Section("App Settings")
  26. <<< SwitchRow("appBadge"){ row in
  27. row.title = "Display App Badge"
  28. row.tag = "appBadge"
  29. row.value = UserDefaultsRepository.appBadge.value
  30. }.onChange { [weak self] row in
  31. guard let value = row.value else { return }
  32. UserDefaultsRepository.appBadge.value = value
  33. // Force main screen update
  34. //guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  35. //mainScreen.nightscoutLoader(forceLoad: true)
  36. // set the appstate to indicate settings change and flags
  37. if let appState = self!.appStateController {
  38. appState.generalSettingsChanged = true
  39. appState.generalSettingsChanges |= GeneralSettingsChangeEnum.appBadgeChange.rawValue
  40. }
  41. }
  42. <<< SwitchRow("persistentNotification") { row in
  43. row.title = "Persistent Notification"
  44. row.value = UserDefaultsRepository.persistentNotification.value
  45. }.onChange { [weak self] row in
  46. guard let value = row.value else { return }
  47. UserDefaultsRepository.persistentNotification.value = value
  48. }
  49. +++ Section("Display Settings")
  50. <<< SwitchRow("forceDarkMode") { row in
  51. row.title = "Force Dark Mode (Restart App)"
  52. row.value = UserDefaultsRepository.forceDarkMode.value
  53. }.onChange { [weak self] row in
  54. guard let value = row.value else { return }
  55. UserDefaultsRepository.forceDarkMode.value = value
  56. }
  57. <<< SwitchRow("showStats") { row in
  58. row.title = "Display Stats"
  59. row.value = UserDefaultsRepository.showStats.value
  60. }.onChange { [weak self] row in
  61. guard let value = row.value else { return }
  62. UserDefaultsRepository.showStats.value = value
  63. // set the appstate to indicate settings change and flags
  64. if let appState = self!.appStateController {
  65. appState.generalSettingsChanged = true
  66. appState.generalSettingsChanges |= GeneralSettingsChangeEnum.showStatsChange.rawValue
  67. }
  68. }
  69. <<< SwitchRow("useIFCC") { row in
  70. row.title = "Use IFCC A1C"
  71. row.value = UserDefaultsRepository.useIFCC.value
  72. }.onChange { [weak self] row in
  73. guard let value = row.value else { return }
  74. UserDefaultsRepository.useIFCC.value = value
  75. // set the appstate to indicate settings change and flags
  76. if let appState = self!.appStateController {
  77. appState.generalSettingsChanged = true
  78. appState.generalSettingsChanges |= GeneralSettingsChangeEnum.useIFCCChange.rawValue
  79. }
  80. }
  81. <<< SwitchRow("showSmallGraph") { row in
  82. row.title = "Display Small Graph"
  83. row.value = UserDefaultsRepository.showSmallGraph.value
  84. }.onChange { [weak self] row in
  85. guard let value = row.value else { return }
  86. UserDefaultsRepository.showSmallGraph.value = value
  87. // set the appstate to indicate settings change and flags
  88. if let appState = self!.appStateController {
  89. appState.generalSettingsChanged = true
  90. appState.generalSettingsChanges |= GeneralSettingsChangeEnum.showSmallGraphChange.rawValue
  91. }
  92. }
  93. <<< SwitchRow("colorBGText") { row in
  94. row.title = "Color Main BG Text"
  95. row.value = UserDefaultsRepository.colorBGText.value
  96. }.onChange { [weak self] row in
  97. guard let value = row.value else { return }
  98. UserDefaultsRepository.colorBGText.value = value
  99. // Force main screen update
  100. //guard let mainScreen = self?.tabBarController!.viewControllers?[0] as? MainViewController else { return }
  101. //mainScreen.setBGTextColor()
  102. // set the appstate to indicate settings change and flags
  103. if let appState = self!.appStateController {
  104. appState.generalSettingsChanged = true
  105. appState.generalSettingsChanges |= GeneralSettingsChangeEnum.colorBGTextChange.rawValue
  106. }
  107. }
  108. <<< SwitchRow("screenlockSwitchState") { row in
  109. row.title = "Keep Screen Active"
  110. row.value = UserDefaultsRepository.screenlockSwitchState.value
  111. }.onChange { [weak self] row in
  112. guard let value = row.value else { return }
  113. UserDefaultsRepository.screenlockSwitchState.value = value
  114. }
  115. <<< SwitchRow("showDisplayName") { row in
  116. row.title = "Show Display Name"
  117. row.value = UserDefaultsRepository.showDisplayName.value
  118. }.onChange { [weak self] row in
  119. guard let value = row.value else { return }
  120. UserDefaultsRepository.showDisplayName.value = value
  121. if let appState = self!.appStateController {
  122. appState.generalSettingsChanged = true
  123. appState.generalSettingsChanges |= GeneralSettingsChangeEnum.showDisplayNameChange.rawValue
  124. }
  125. }
  126. +++ Section("Speak BG Settings")
  127. <<< SwitchRow("speakBG") { row in
  128. row.title = "Speak BG"
  129. row.value = UserDefaultsRepository.speakBG.value
  130. }.onChange { [weak self] row in
  131. guard let value = row.value else { return }
  132. UserDefaultsRepository.speakBG.value = value
  133. self?.updateSpeakBGSettingsVisibility()
  134. }
  135. <<< PushRow<String>("speakLanguage") { row in
  136. row.title = "Speaking Language"
  137. row.options = ["en", "it", "sk", "sv"]
  138. row.value = UserDefaultsRepository.speakLanguage.value
  139. row.displayValueFor = { value in
  140. switch value {
  141. case "en": return "English"
  142. case "it": return "Italian"
  143. case "sk": return "Slovak"
  144. case "sv": return "Swedish"
  145. default: return "Unknown"
  146. }
  147. }
  148. row.hidden = Condition.function(["speakBG"], { form in
  149. let speakBGRow: SwitchRow! = form.rowBy(tag: "speakBG")
  150. return !(speakBGRow.value ?? false)
  151. })
  152. row.presentationMode = PresentationMode.presentModally(
  153. controllerProvider: ControllerProvider.callback {
  154. return SelectorViewController<SelectorRow<PushSelectorCell<String>>> { _ in }
  155. },
  156. onDismiss: { vc in
  157. vc.dismiss(animated: true)
  158. })
  159. }.onChange { row in
  160. guard let value = row.value else { return }
  161. UserDefaultsRepository.speakLanguage.value = value
  162. }
  163. <<< SwitchRow("speakBGAlways") { row in
  164. row.title = "Always"
  165. row.value = UserDefaultsRepository.speakBGAlways.value
  166. }.onChange { [weak self] row in
  167. guard let value = row.value else { return }
  168. UserDefaultsRepository.speakBGAlways.value = value
  169. self?.updateSpeakBGSettingsVisibility()
  170. }
  171. <<< SwitchRow("speakLowBG") { row in
  172. row.title = "Low"
  173. row.value = UserDefaultsRepository.speakLowBG.value
  174. }.onChange { [weak self] row in
  175. self?.handleLowProactiveLowToggle(row: row, opposingRowTag: "speakProactiveLowBG")
  176. }
  177. <<< SwitchRow("speakProactiveLowBG") { row in
  178. row.title = "Proactive Low"
  179. row.value = UserDefaultsRepository.speakProactiveLowBG.value
  180. }.onChange { [weak self] row in
  181. self?.handleLowProactiveLowToggle(row: row, opposingRowTag: "speakLowBG")
  182. }
  183. <<< StepperRow("speakLowBGLimit") { row in
  184. row.title = "Low BG Limit"
  185. row.cell.stepper.stepValue = 1
  186. row.cell.stepper.minimumValue = 40
  187. row.cell.stepper.maximumValue = 108
  188. row.value = Double(UserDefaultsRepository.speakLowBGLimit.value)
  189. row.displayValueFor = { value in
  190. guard let value = value else { return nil }
  191. return Localizer.toDisplayUnits(String(value))
  192. }
  193. // Visibility depends on either 'speakLowBG' or 'speakProactiveLowBG' being true
  194. row.hidden = Condition.function(["speakLowBG", "speakProactiveLowBG", "speakBG", "speakBGAlways"], { form in
  195. let speakBGRow: SwitchRow! = form.rowBy(tag: "speakBG")
  196. let speakBGAlwaysRow: SwitchRow! = form.rowBy(tag: "speakBGAlways")
  197. let speakLowBGRow: SwitchRow! = form.rowBy(tag: "speakLowBG")
  198. let speakProactiveLowBGRow: SwitchRow! = form.rowBy(tag: "speakProactiveLowBG")
  199. return !(speakLowBGRow.value ?? false) && !(speakProactiveLowBGRow.value ?? false) || !(speakBGRow.value ?? true) || (speakBGAlwaysRow.value ?? false)
  200. })
  201. }.onChange { [weak self] row in
  202. guard let value = row.value else { return }
  203. UserDefaultsRepository.speakLowBGLimit.value = Float(value)
  204. }
  205. <<< StepperRow("speakFastDropDelta") { row in
  206. row.title = "Fast Drop Delta"
  207. row.cell.stepper.stepValue = 1
  208. row.cell.stepper.minimumValue = 3
  209. row.cell.stepper.maximumValue = 20
  210. row.value = Double(UserDefaultsRepository.speakFastDropDelta.value)
  211. row.displayValueFor = { value in
  212. guard let value = value else { return nil }
  213. return Localizer.toDisplayUnits(String(value))
  214. }
  215. // Visibility depends on 'speakProactiveLowBG' being true
  216. row.hidden = Condition.function(["speakProactiveLowBG", "speakBG", "speakBGAlways"], { form in
  217. let speakBGRow: SwitchRow! = form.rowBy(tag: "speakBG")
  218. let speakBGAlwaysRow: SwitchRow! = form.rowBy(tag: "speakBGAlways")
  219. let speakProactiveLowBGRow: SwitchRow! = form.rowBy(tag: "speakProactiveLowBG")
  220. return !(speakProactiveLowBGRow.value ?? false) || !(speakBGRow.value ?? true) || (speakBGAlwaysRow.value ?? false)
  221. })
  222. }.onChange { [weak self] row in
  223. guard let value = row.value else { return }
  224. UserDefaultsRepository.speakFastDropDelta.value = Float(value)
  225. }
  226. <<< SwitchRow("speakHighBG") { row in
  227. row.title = "High"
  228. row.value = UserDefaultsRepository.speakHighBG.value
  229. }.onChange { row in
  230. guard let value = row.value else { return }
  231. UserDefaultsRepository.speakHighBG.value = value
  232. }
  233. <<< StepperRow("speakHighBGLimit") { row in
  234. row.title = "High BG Limit"
  235. row.cell.stepper.stepValue = 1
  236. row.cell.stepper.minimumValue = 140
  237. row.cell.stepper.maximumValue = 300
  238. row.value = Double(UserDefaultsRepository.speakHighBGLimit.value)
  239. row.displayValueFor = { value in
  240. guard let value = value else { return nil }
  241. return Localizer.toDisplayUnits(String(value))
  242. }
  243. // Visibility depends on 'speakHighBG' or 'speakProactiveLowBG' being true
  244. row.hidden = Condition.function(["speakHighBG", "speakProactiveLowBG", "speakBG", "speakBGAlways"], { form in
  245. let speakBGRow: SwitchRow! = form.rowBy(tag: "speakBG")
  246. let speakBGAlwaysRow: SwitchRow! = form.rowBy(tag: "speakBGAlways")
  247. let speakHighBGRow: SwitchRow! = form.rowBy(tag: "speakHighBG")
  248. let speakProactiveLowBGRow: SwitchRow! = form.rowBy(tag: "speakProactiveLowBG")
  249. return !(speakHighBGRow.value ?? false) && !(speakProactiveLowBGRow.value ?? false) || !(speakBGRow.value ?? true) || (speakBGAlwaysRow.value ?? false)
  250. })
  251. }.onChange { [weak self] row in
  252. guard let value = row.value else { return }
  253. UserDefaultsRepository.speakHighBGLimit.value = Float(value)
  254. }
  255. +++ ButtonRow() {
  256. $0.title = "DONE"
  257. }.onCellSelection { (row, arg) in
  258. self.dismiss(animated:true, completion: nil)
  259. }
  260. // Call to update initial visibility based on current settings
  261. updateSpeakBGSettingsVisibility()
  262. }
  263. func updateSpeakBGSettingsVisibility() {
  264. let alwaysOn = UserDefaultsRepository.speakBGAlways.value
  265. let speakBGOn = UserDefaultsRepository.speakBG.value
  266. // Determine visibility for "Always", "Low", "Proactive Low", and "High" based on "Speak BG" and "Always"
  267. let shouldHideAlways = !speakBGOn
  268. let shouldHideSettings = alwaysOn || !speakBGOn
  269. form.rowBy(tag: "speakBGAlways")?.hidden = Condition(booleanLiteral: shouldHideAlways)
  270. form.rowBy(tag: "speakBGAlways")?.evaluateHidden()
  271. ["speakLowBG", "speakProactiveLowBG", "speakHighBG"].forEach { tag in
  272. if let row = form.rowBy(tag: tag) {
  273. row.hidden = Condition(booleanLiteral: shouldHideSettings)
  274. row.evaluateHidden()
  275. row.updateCell()
  276. }
  277. }
  278. }
  279. func handleLowProactiveLowToggle(row: BaseRow, opposingRowTag: String) {
  280. guard let switchRow = row as? SwitchRow, let value = switchRow.value else { return }
  281. // Update the UserDefaults value for the current row.
  282. if row.tag == "speakLowBG" {
  283. UserDefaultsRepository.speakLowBG.value = value
  284. } else if row.tag == "speakProactiveLowBG" {
  285. UserDefaultsRepository.speakProactiveLowBG.value = value
  286. }
  287. // If the current switch is being turned ON, turn the opposing switch OFF.
  288. if value {
  289. if let opposingRow = form.rowBy(tag: opposingRowTag) as? SwitchRow {
  290. opposingRow.value = false
  291. opposingRow.updateCell()
  292. // Update the UserDefaults value for the opposing row.
  293. if opposingRowTag == "speakLowBG" {
  294. UserDefaultsRepository.speakLowBG.value = false
  295. } else if opposingRowTag == "speakProactiveLowBG" {
  296. UserDefaultsRepository.speakProactiveLowBG.value = false
  297. }
  298. }
  299. }
  300. }
  301. // Update the "Speak BG" SwitchRow value in the General Settings when the app enters the foreground. This ensures that the switch reflects the current setting in UserDefaultsRepository, even if it was changed using the Home Screen Quick Action while the app was in the background.
  302. @objc func handleAppWillEnterForeground() {
  303. if let row = self.form.rowBy(tag: "speakBG") as? SwitchRow {
  304. row.value = UserDefaultsRepository.speakBG.value
  305. row.updateCell()
  306. }
  307. }
  308. deinit {
  309. NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
  310. }
  311. }