Storage.swift 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. // LoopFollow
  2. // Storage.swift
  3. import Foundation
  4. import HealthKit
  5. import UIKit
  6. /*
  7. Observable persistant storage
  8. */
  9. class Storage {
  10. var remoteType = StorageValue<RemoteType>(key: "remoteType", defaultValue: .none)
  11. var deviceToken = StorageValue<String>(key: "deviceToken", defaultValue: "")
  12. var expirationDate = StorageValue<Date?>(key: "expirationDate", defaultValue: nil)
  13. var sharedSecret = StorageValue<String>(key: "sharedSecret", defaultValue: "")
  14. var productionEnvironment = StorageValue<Bool>(key: "productionEnvironment", defaultValue: false)
  15. var remoteApnsKey = StorageValue<String>(key: "remoteApnsKey", defaultValue: "")
  16. var teamId = StorageValue<String?>(key: "teamId", defaultValue: nil)
  17. var remoteKeyId = StorageValue<String>(key: "remoteKeyId", defaultValue: "")
  18. var lfApnsKey = StorageValue<String>(key: "lfApnsKey", defaultValue: "")
  19. var lfKeyId = StorageValue<String>(key: "lfKeyId", defaultValue: "")
  20. var bundleId = StorageValue<String>(key: "bundleId", defaultValue: "")
  21. var user = StorageValue<String>(key: "user", defaultValue: "")
  22. var maxBolus = SecureStorageValue<HKQuantity>(key: "maxBolus", defaultValue: HKQuantity(unit: .internationalUnit(), doubleValue: 1.0))
  23. var maxCarbs = SecureStorageValue<HKQuantity>(key: "maxCarbs", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
  24. var maxProtein = SecureStorageValue<HKQuantity>(key: "maxProtein", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
  25. var maxFat = SecureStorageValue<HKQuantity>(key: "maxFat", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
  26. var mealWithBolus = StorageValue<Bool>(key: "mealWithBolus", defaultValue: false)
  27. var mealWithFatProtein = StorageValue<Bool>(key: "mealWithFatProtein", defaultValue: false)
  28. // TODO: This flag can be deleted in March 2027. Check the commit for other places to cleanup.
  29. var hasSeenFatProteinOrderChange = StorageValue<Bool>(key: "hasSeenFatProteinOrderChange", defaultValue: true)
  30. var backgroundRefreshType = StorageValue<BackgroundRefreshType>(key: "backgroundRefreshType", defaultValue: .silentTune)
  31. var selectedBLEDevice = StorageValue<BLEDevice?>(key: "selectedBLEDevice", defaultValue: nil)
  32. var debugLogLevel = StorageValue<Bool>(key: "debugLogLevel", defaultValue: true)
  33. var contactTrend = StorageValue<ContactIncludeOption>(key: "contactTrend", defaultValue: .off)
  34. var contactDelta = StorageValue<ContactIncludeOption>(key: "contactDelta", defaultValue: .off)
  35. var contactIOB = StorageValue<ContactIncludeOption>(key: "contactIOB", defaultValue: .off)
  36. var contactTrendTarget = StorageValue<ContactType>(key: "contactTrendTarget", defaultValue: .BG)
  37. var contactDeltaTarget = StorageValue<ContactType>(key: "contactDeltaTarget", defaultValue: .BG)
  38. var contactIOBTarget = StorageValue<ContactType>(key: "contactIOBTarget", defaultValue: .BG)
  39. var contactEnabled = StorageValue<Bool>(key: "contactEnabled", defaultValue: false)
  40. var contactBackgroundColor = StorageValue<String>(key: "contactBackgroundColor", defaultValue: ContactColorOption.black.rawValue)
  41. var contactTextColor = StorageValue<String>(key: "contactTextColor", defaultValue: ContactColorOption.white.rawValue)
  42. var contactColorMode = StorageValue<ContactColorMode>(key: "contactColorMode", defaultValue: .staticColor)
  43. var sensorScheduleOffset = StorageValue<Double?>(key: "sensorScheduleOffset", defaultValue: nil)
  44. var alarms = StorageValue<[Alarm]>(key: "alarms", defaultValue: [])
  45. var alarmConfiguration = StorageValue<AlarmConfiguration>(key: "alarmConfiguration", defaultValue: .default)
  46. var lastOverrideStartNotified = StorageValue<TimeInterval?>(key: "lastOverrideStartNotified", defaultValue: nil)
  47. var lastOverrideEndNotified = StorageValue<TimeInterval?>(key: "lastOverrideEndNotified", defaultValue: nil)
  48. var lastTempTargetStartNotified = StorageValue<TimeInterval?>(key: "lastTempTargetStartNotified", defaultValue: nil)
  49. var lastTempTargetEndNotified = StorageValue<TimeInterval?>(key: "lastTempTargetEndNotified", defaultValue: nil)
  50. var lastRecBolusNotified = StorageValue<Double?>(key: "lastRecBolusNotified", defaultValue: nil)
  51. var lastCOBNotified = StorageValue<Double?>(key: "lastCOBNotified", defaultValue: nil)
  52. var lastMissedBolusNotified = StorageValue<Date?>(key: "lastMissedBolusNotified", defaultValue: nil)
  53. var pendingFutureCarbs = StorageValue<[PendingFutureCarb]>(key: "pendingFutureCarbs", defaultValue: [])
  54. // General Settings [BEGIN]
  55. var appBadge = StorageValue<Bool>(key: "appBadge", defaultValue: true)
  56. var colorBGText = StorageValue<Bool>(key: "colorBGText", defaultValue: true)
  57. var appearanceMode = StorageValue<AppearanceMode>(key: "appearanceMode", defaultValue: .dark)
  58. var showStats = StorageValue<Bool>(key: "showStats", defaultValue: true)
  59. var useIFCC = StorageValue<Bool>(key: "useIFCC", defaultValue: false)
  60. var showSmallGraph = StorageValue<Bool>(key: "showSmallGraph", defaultValue: true)
  61. var screenlockSwitchState = StorageValue<Bool>(key: "screenlockSwitchState", defaultValue: true)
  62. var showDisplayName = StorageValue<Bool>(key: "showDisplayName", defaultValue: false)
  63. var snoozerEmoji = StorageValue<Bool>(key: "snoozerEmoji", defaultValue: false)
  64. var forcePortraitMode = StorageValue<Bool>(key: "forcePortraitMode", defaultValue: false)
  65. var speakBG = StorageValue<Bool>(key: "speakBG", defaultValue: false)
  66. var speakBGAlways = StorageValue<Bool>(key: "speakBGAlways", defaultValue: true)
  67. var speakLowBG = StorageValue<Bool>(key: "speakLowBG", defaultValue: false)
  68. var speakProactiveLowBG = StorageValue<Bool>(key: "speakProactiveLowBG", defaultValue: false)
  69. var speakFastDropDelta = StorageValue<Double>(key: "speakFastDropDelta", defaultValue: 10.0)
  70. var speakLowBGLimit = StorageValue<Double>(key: "speakLowBGLimit", defaultValue: 72.0)
  71. var speakHighBGLimit = StorageValue<Double>(key: "speakHighBGLimit", defaultValue: 180.0)
  72. var speakHighBG = StorageValue<Bool>(key: "speakHighBG", defaultValue: false)
  73. var speakLanguage = StorageValue<String>(key: "speakLanguage", defaultValue: "en")
  74. // General Settings [END]
  75. // Live Activity glucose state
  76. var lastBgReadingTimeSeconds = StorageValue<TimeInterval?>(key: "lastBgReadingTimeSeconds", defaultValue: nil)
  77. var lastDeltaMgdl = StorageValue<Double?>(key: "lastDeltaMgdl", defaultValue: nil)
  78. var lastTrendCode = StorageValue<String?>(key: "lastTrendCode", defaultValue: nil)
  79. var lastIOB = StorageValue<Double?>(key: "lastIOB", defaultValue: nil)
  80. var lastCOB = StorageValue<Double?>(key: "lastCOB", defaultValue: nil)
  81. var projectedBgMgdl = StorageValue<Double?>(key: "projectedBgMgdl", defaultValue: nil)
  82. // Live Activity extended InfoType data
  83. var lastBasal = StorageValue<String>(key: "lastBasal", defaultValue: "")
  84. var lastPumpReservoirU = StorageValue<Double?>(key: "lastPumpReservoirU", defaultValue: nil)
  85. var lastAutosens = StorageValue<Double?>(key: "lastAutosens", defaultValue: nil)
  86. var lastTdd = StorageValue<Double?>(key: "lastTdd", defaultValue: nil)
  87. var lastTargetLowMgdl = StorageValue<Double?>(key: "lastTargetLowMgdl", defaultValue: nil)
  88. var lastTargetHighMgdl = StorageValue<Double?>(key: "lastTargetHighMgdl", defaultValue: nil)
  89. var lastIsfMgdlPerU = StorageValue<Double?>(key: "lastIsfMgdlPerU", defaultValue: nil)
  90. var lastCarbRatio = StorageValue<Double?>(key: "lastCarbRatio", defaultValue: nil)
  91. var lastCarbsToday = StorageValue<Double?>(key: "lastCarbsToday", defaultValue: nil)
  92. var lastProfileName = StorageValue<String>(key: "lastProfileName", defaultValue: "")
  93. var iageInsertTime = StorageValue<TimeInterval>(key: "iageInsertTime", defaultValue: 0)
  94. var lastMinBgMgdl = StorageValue<Double?>(key: "lastMinBgMgdl", defaultValue: nil)
  95. var lastMaxBgMgdl = StorageValue<Double?>(key: "lastMaxBgMgdl", defaultValue: nil)
  96. // Live Activity
  97. var laEnabled = StorageValue<Bool>(key: "laEnabled", defaultValue: false)
  98. var laRenewBy = StorageValue<TimeInterval>(key: "laRenewBy", defaultValue: 0)
  99. var laRenewalFailed = StorageValue<Bool>(key: "laRenewalFailed", defaultValue: false)
  100. var laPushToStartToken = StorageValue<String>(key: "laPushToStartToken", defaultValue: "")
  101. var laLastPushToStartAt = StorageValue<TimeInterval>(key: "laLastPushToStartAt", defaultValue: 0)
  102. var laPushToStartBackoff = StorageValue<TimeInterval>(key: "laPushToStartBackoff", defaultValue: 0)
  103. // Graph Settings [BEGIN]
  104. var showDots = StorageValue<Bool>(key: "showDots", defaultValue: true)
  105. var showLines = StorageValue<Bool>(key: "showLines", defaultValue: true)
  106. var showValues = StorageValue<Bool>(key: "showValues", defaultValue: true)
  107. var showAbsorption = StorageValue<Bool>(key: "showAbsorption", defaultValue: true)
  108. var showDIALines = StorageValue<Bool>(key: "showDIAMarkers", defaultValue: true)
  109. var show30MinLine = StorageValue<Bool>(key: "show30MinLine", defaultValue: false)
  110. var show90MinLine = StorageValue<Bool>(key: "show90MinLine", defaultValue: false)
  111. var showMidnightLines = StorageValue<Bool>(key: "showMidnightMarkers", defaultValue: false)
  112. var showYesterdayLine = StorageValue<Bool>(key: "showYesterdayLine", defaultValue: false)
  113. var smallGraphTreatments = StorageValue<Bool>(key: "smallGraphTreatments", defaultValue: true)
  114. var smallGraphHeight = StorageValue<Int>(key: "smallGraphHeight", defaultValue: 40)
  115. var predictionToLoad = StorageValue<Double>(key: "predictionToLoad", defaultValue: 1.0)
  116. var predictionDisplayType = StorageValue<PredictionDisplayType>(key: "predictionDisplayType", defaultValue: .cone)
  117. var minBasalScale = StorageValue<Double>(key: "minBasalScale", defaultValue: 5.0)
  118. var minBGScale = StorageValue<Double>(key: "minBGScale", defaultValue: 250.0)
  119. var lowLine = StorageValue<Double>(key: "lowLine", defaultValue: 70.0)
  120. var highLine = StorageValue<Double>(key: "highLine", defaultValue: 180.0)
  121. var downloadDays = StorageValue<Int>(key: "downloadDays", defaultValue: 1)
  122. var graphTimeZoneEnabled = StorageValue<Bool>(key: "graphTimeZoneEnabled", defaultValue: false)
  123. var graphTimeZoneIdentifier = StorageValue<String>(key: "graphTimeZoneIdentifier", defaultValue: TimeZone.current.identifier)
  124. // Graph Settings [END]
  125. // Calendar entries [BEGIN]
  126. var writeCalendarEvent = StorageValue<Bool>(key: "writeCalendarEvent", defaultValue: false)
  127. var calendarIdentifier = StorageValue<String>(key: "calendarIdentifier", defaultValue: "")
  128. var watchLine1 = StorageValue<String>(key: "watchLine1", defaultValue: "%BG% %DIRECTION% %DELTA% %MINAGO%")
  129. var watchLine2 = StorageValue<String>(key: "watchLine2", defaultValue: "C:%COB% I:%IOB% B:%BASAL%")
  130. // Calendar entries [END]
  131. // MARK: - Dexcom Share --------------------------------------------------------
  132. var shareUserName = StorageValue<String>(key: "shareUserName", defaultValue: "")
  133. var sharePassword = StorageValue<String>(key: "sharePassword", defaultValue: "")
  134. var shareServer = StorageValue<String>(key: "shareServer", defaultValue: "US")
  135. // MARK: - Graph ---------------------------------------------------------------
  136. var chartScaleX = StorageValue<Double>(key: "chartScaleX", defaultValue: 18.0)
  137. // MARK: - Advanced settings ---------------------------------------------------
  138. var downloadTreatments = StorageValue<Bool>(key: "downloadTreatments", defaultValue: true)
  139. var downloadPrediction = StorageValue<Bool>(key: "downloadPrediction", defaultValue: true)
  140. var graphOtherTreatments = StorageValue<Bool>(key: "graphOtherTreatments", defaultValue: true)
  141. var graphBasal = StorageValue<Bool>(key: "graphBasal", defaultValue: true)
  142. var graphBolus = StorageValue<Bool>(key: "graphBolus", defaultValue: true)
  143. var graphCarbs = StorageValue<Bool>(key: "graphCarbs", defaultValue: true)
  144. var bgUpdateDelay = StorageValue<Int>(key: "bgUpdateDelay", defaultValue: 10)
  145. // MARK: - Insert times (sensor / pump) ---------------------------------------
  146. var cageInsertTime = StorageValue<TimeInterval>(key: "cageInsertTime", defaultValue: 0)
  147. var sageInsertTime = StorageValue<TimeInterval>(key: "sageInsertTime", defaultValue: 0)
  148. // MARK: - Version-info ---------------------------
  149. var cachedForVersion = StorageValue<String?>(key: "cachedForVersion", defaultValue: nil)
  150. var latestVersion = StorageValue<String?>(key: "latestVersion", defaultValue: nil)
  151. var latestVersionChecked = StorageValue<Date?>(key: "latestVersionChecked", defaultValue: nil)
  152. var currentVersionBlackListed = StorageValue<Bool>(key: "currentVersionBlackListed", defaultValue: false)
  153. var lastBlacklistNotificationShown = StorageValue<Date?>(key: "lastBlacklistNotificationShown", defaultValue: nil)
  154. var lastVersionUpdateNotificationShown = StorageValue<Date?>(key: "lastVersionUpdateNotificationShown", defaultValue: nil)
  155. var lastExpirationNotificationShown = StorageValue<Date?>(key: "lastExpirationNotificationShown", defaultValue: nil)
  156. // MARK: - Telemetry -----------------------------------------------------------
  157. // See LoopFollow/Helpers/Telemetry.swift.
  158. var telemetryEnabled = StorageValue<Bool>(key: "telemetryEnabled", defaultValue: true)
  159. var telemetryConsentDecisionMade = StorageValue<Bool>(key: "telemetryConsentDecisionMade", defaultValue: false)
  160. var telemetryLastSentAt = StorageValue<Date?>(key: "telemetryLastSentAt", defaultValue: nil)
  161. var telemetryLastSentSha = StorageValue<String>(key: "telemetryLastSentSha", defaultValue: "")
  162. // Sliding 7-day window of cold-launch timestamps.
  163. var telemetryColdLaunchTimes = StorageValue<[Date]>(key: "telemetryColdLaunchTimes", defaultValue: [])
  164. var hideInfoTable = StorageValue<Bool>(key: "hideInfoTable", defaultValue: false)
  165. var token = StorageValue<String>(key: "token", defaultValue: "")
  166. var units = StorageValue<String>(key: "units", defaultValue: "mg/dL")
  167. var hasConfiguredUnits = StorageValue<Bool>(key: "hasConfiguredUnits", defaultValue: false)
  168. var infoSort = StorageValue<[Int]>(key: "infoSort", defaultValue: InfoType.allCases.map(\.sortOrder))
  169. var infoVisible = StorageValue<[Bool]>(key: "infoVisible", defaultValue: InfoType.allCases.map(\.defaultVisible))
  170. var url = StorageValue<String>(key: "url", defaultValue: "")
  171. var device = StorageValue<String>(key: "device", defaultValue: "")
  172. var nsWriteAuth = StorageValue<Bool>(key: "nsWriteAuth", defaultValue: false)
  173. var nsAdminAuth = StorageValue<Bool>(key: "nsAdminAuth", defaultValue: false)
  174. var webSocketEnabled = StorageValue<Bool>(key: "webSocketEnabled", defaultValue: true)
  175. // When adding a new migration step in `runMigrationsIfNeeded()`, bump this default
  176. // to the new latest step number so fresh installs skip all migrations. Other defaults
  177. // in this file must reflect the post-migration final state for a fresh install.
  178. var migrationStep = StorageValue<Int>(key: "migrationStep", defaultValue: 9)
  179. var persistentNotification = StorageValue<Bool>(key: "persistentNotification", defaultValue: false)
  180. var persistentNotificationLastBGTime = StorageValue<Date>(key: "persistentNotificationLastBGTime", defaultValue: .distantPast)
  181. var lastLoopingChecked = StorageValue<Date?>(key: "lastLoopingChecked", defaultValue: nil)
  182. var lastBGChecked = StorageValue<Date?>(key: "lastBGChecked", defaultValue: nil)
  183. var lastLoopTime = StorageValue<TimeInterval>(key: "lastLoopTime", defaultValue: 0)
  184. // Tab positions - which position each item is in (positions 1-4 are customizable, 5 is always Menu)
  185. var homePosition = StorageValue<TabPosition>(key: "homePosition", defaultValue: .position1)
  186. var alarmsPosition = StorageValue<TabPosition>(key: "alarmsPosition", defaultValue: .position2)
  187. var snoozerPosition = StorageValue<TabPosition>(key: "snoozerPosition", defaultValue: .position3)
  188. var nightscoutPosition = StorageValue<TabPosition>(key: "nightscoutPosition", defaultValue: .position4)
  189. var remotePosition = StorageValue<TabPosition>(key: "remotePosition", defaultValue: .menu)
  190. var statisticsPosition = StorageValue<TabPosition>(key: "statisticsPosition", defaultValue: .menu)
  191. var treatmentsPosition = StorageValue<TabPosition>(key: "treatmentsPosition", defaultValue: .menu)
  192. var loopAPNSQrCodeURL = StorageValue<String>(key: "loopAPNSQrCodeURL", defaultValue: "")
  193. var bolusIncrement = SecureStorageValue<HKQuantity>(key: "bolusIncrement", defaultValue: HKQuantity(unit: .internationalUnit(), doubleValue: 0.05))
  194. var bolusIncrementDetected = StorageValue<Bool>(key: "bolusIncrementDetected", defaultValue: false)
  195. var remoteBolusHistory = StorageValue<[RemoteBolusHistoryEntry]>(key: "remoteBolusHistory", defaultValue: [])
  196. var remoteMealHistory = StorageValue<[RemoteMealHistoryEntry]>(key: "remoteMealHistory", defaultValue: [])
  197. // Statistics display preferences
  198. var showGMI = StorageValue<Bool>(key: "showGMI", defaultValue: true)
  199. var showStdDev = StorageValue<Bool>(key: "showStdDev", defaultValue: true)
  200. var showTITR = StorageValue<Bool>(key: "showTITR", defaultValue: false)
  201. var timeInRangeModeRaw = StorageValue<String>(key: "timeInRangeMode", defaultValue: "TIR")
  202. static let shared = Storage()
  203. private init() {}
  204. /// Set to true at launch if isProtectedDataAvailable was false (BFU state).
  205. /// Consumed and cleared on the first foreground after that launch.
  206. var needsBFUReload = false
  207. /// Re-reads every StorageValue from UserDefaults, firing @Published only where the value
  208. /// actually changed. Call this when foregrounding after a Before-First-Unlock (BFU) background
  209. /// launch, where Storage was initialized while UserDefaults was encrypted and all values were
  210. /// cached as their defaults.
  211. ///
  212. /// `migrationStep` is intentionally excluded: viewDidLoad writes it to the latest step during
  213. /// the BFU launch; if we reloaded it and the flush had somehow not landed yet, migrations would re-run.
  214. ///
  215. /// SecureStorageValue properties (maxBolus, maxCarbs, maxProtein, maxFat, bolusIncrement) are
  216. /// not covered here — SecureStorageValue does not implement reload() and Keychain has the same
  217. /// BFU inaccessibility; that is a separate problem.
  218. func reloadAll() {
  219. remoteType.reload()
  220. deviceToken.reload()
  221. expirationDate.reload()
  222. sharedSecret.reload()
  223. productionEnvironment.reload()
  224. remoteApnsKey.reload()
  225. teamId.reload()
  226. remoteKeyId.reload()
  227. lfApnsKey.reload()
  228. lfKeyId.reload()
  229. bundleId.reload()
  230. user.reload()
  231. mealWithBolus.reload()
  232. mealWithFatProtein.reload()
  233. hasSeenFatProteinOrderChange.reload()
  234. backgroundRefreshType.reload()
  235. selectedBLEDevice.reload()
  236. debugLogLevel.reload()
  237. contactTrend.reload()
  238. contactDelta.reload()
  239. contactEnabled.reload()
  240. contactBackgroundColor.reload()
  241. contactTextColor.reload()
  242. sensorScheduleOffset.reload()
  243. alarms.reload()
  244. alarmConfiguration.reload()
  245. lastOverrideStartNotified.reload()
  246. lastOverrideEndNotified.reload()
  247. lastTempTargetStartNotified.reload()
  248. lastTempTargetEndNotified.reload()
  249. lastRecBolusNotified.reload()
  250. lastCOBNotified.reload()
  251. lastMissedBolusNotified.reload()
  252. appBadge.reload()
  253. colorBGText.reload()
  254. appearanceMode.reload()
  255. showStats.reload()
  256. useIFCC.reload()
  257. showSmallGraph.reload()
  258. screenlockSwitchState.reload()
  259. showDisplayName.reload()
  260. snoozerEmoji.reload()
  261. forcePortraitMode.reload()
  262. speakBG.reload()
  263. speakBGAlways.reload()
  264. speakLowBG.reload()
  265. speakProactiveLowBG.reload()
  266. speakFastDropDelta.reload()
  267. speakLowBGLimit.reload()
  268. speakHighBGLimit.reload()
  269. speakHighBG.reload()
  270. speakLanguage.reload()
  271. lastBgReadingTimeSeconds.reload()
  272. lastDeltaMgdl.reload()
  273. lastTrendCode.reload()
  274. lastIOB.reload()
  275. lastCOB.reload()
  276. projectedBgMgdl.reload()
  277. lastBasal.reload()
  278. lastPumpReservoirU.reload()
  279. lastAutosens.reload()
  280. lastTdd.reload()
  281. lastTargetLowMgdl.reload()
  282. lastTargetHighMgdl.reload()
  283. lastIsfMgdlPerU.reload()
  284. lastCarbRatio.reload()
  285. lastCarbsToday.reload()
  286. lastProfileName.reload()
  287. iageInsertTime.reload()
  288. lastMinBgMgdl.reload()
  289. lastMaxBgMgdl.reload()
  290. laEnabled.reload()
  291. laRenewBy.reload()
  292. laRenewalFailed.reload()
  293. laPushToStartToken.reload()
  294. laLastPushToStartAt.reload()
  295. laPushToStartBackoff.reload()
  296. showDots.reload()
  297. showLines.reload()
  298. showValues.reload()
  299. showAbsorption.reload()
  300. showDIALines.reload()
  301. show30MinLine.reload()
  302. show90MinLine.reload()
  303. showMidnightLines.reload()
  304. smallGraphTreatments.reload()
  305. smallGraphHeight.reload()
  306. predictionToLoad.reload()
  307. predictionDisplayType.reload()
  308. minBasalScale.reload()
  309. minBGScale.reload()
  310. lowLine.reload()
  311. highLine.reload()
  312. downloadDays.reload()
  313. graphTimeZoneEnabled.reload()
  314. graphTimeZoneIdentifier.reload()
  315. writeCalendarEvent.reload()
  316. calendarIdentifier.reload()
  317. watchLine1.reload()
  318. watchLine2.reload()
  319. shareUserName.reload()
  320. sharePassword.reload()
  321. shareServer.reload()
  322. chartScaleX.reload()
  323. downloadTreatments.reload()
  324. downloadPrediction.reload()
  325. graphOtherTreatments.reload()
  326. graphBasal.reload()
  327. graphBolus.reload()
  328. graphCarbs.reload()
  329. bgUpdateDelay.reload()
  330. cageInsertTime.reload()
  331. sageInsertTime.reload()
  332. cachedForVersion.reload()
  333. latestVersion.reload()
  334. latestVersionChecked.reload()
  335. currentVersionBlackListed.reload()
  336. lastBlacklistNotificationShown.reload()
  337. lastVersionUpdateNotificationShown.reload()
  338. lastExpirationNotificationShown.reload()
  339. hideInfoTable.reload()
  340. token.reload()
  341. units.reload()
  342. infoSort.reload()
  343. infoVisible.reload()
  344. url.reload()
  345. device.reload()
  346. nsWriteAuth.reload()
  347. nsAdminAuth.reload()
  348. // migrationStep intentionally excluded — see method comment above.
  349. persistentNotification.reload()
  350. persistentNotificationLastBGTime.reload()
  351. lastLoopingChecked.reload()
  352. lastBGChecked.reload()
  353. lastLoopTime.reload()
  354. homePosition.reload()
  355. alarmsPosition.reload()
  356. snoozerPosition.reload()
  357. nightscoutPosition.reload()
  358. remotePosition.reload()
  359. statisticsPosition.reload()
  360. treatmentsPosition.reload()
  361. loopAPNSQrCodeURL.reload()
  362. bolusIncrementDetected.reload()
  363. remoteBolusHistory.reload()
  364. remoteMealHistory.reload()
  365. showGMI.reload()
  366. showStdDev.reload()
  367. showTITR.reload()
  368. timeInRangeModeRaw.reload()
  369. }
  370. // MARK: - Tab Position Helpers
  371. /// Get the position for a given tab item
  372. func position(for item: TabItem) -> TabPosition {
  373. switch item {
  374. case .home: homePosition.value
  375. case .alarms: alarmsPosition.value
  376. case .remote: remotePosition.value
  377. case .nightscout: nightscoutPosition.value
  378. case .snoozer: snoozerPosition.value
  379. case .stats: statisticsPosition.value
  380. case .treatments: treatmentsPosition.value
  381. }
  382. }
  383. /// Set the position for a given tab item
  384. func setPosition(_ position: TabPosition, for item: TabItem) {
  385. switch item {
  386. case .home: homePosition.value = position
  387. case .alarms: alarmsPosition.value = position
  388. case .remote: remotePosition.value = position
  389. case .nightscout: nightscoutPosition.value = position
  390. case .snoozer: snoozerPosition.value = position
  391. case .stats: statisticsPosition.value = position
  392. case .treatments: treatmentsPosition.value = position
  393. }
  394. }
  395. /// Get the tab item at a specific position (nil if no item at that position)
  396. func tabItem(at position: TabPosition) -> TabItem? {
  397. for item in TabItem.allCases {
  398. // Use normalized comparison to handle legacy values (.more, .disabled -> .menu)
  399. if self.position(for: item).normalized == position.normalized {
  400. return item
  401. }
  402. }
  403. return nil
  404. }
  405. /// Get all items in the Menu (position 5)
  406. func itemsInMenu() -> [TabItem] {
  407. TabItem.featureOrder.filter { position(for: $0).normalized == .menu }
  408. }
  409. /// Get items ordered by their position in the tab bar (positions 1-4)
  410. func orderedTabBarItems() -> [TabItem] {
  411. TabPosition.customizablePositions.compactMap { tabItem(at: $0) }
  412. }
  413. }