Storage.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // LoopFollow
  2. // Storage.swift
  3. import Foundation
  4. import HealthKit
  5. /*
  6. Observable persistant storage
  7. */
  8. class Storage {
  9. var remoteType = StorageValue<RemoteType>(key: "remoteType", defaultValue: .none)
  10. var deviceToken = StorageValue<String>(key: "deviceToken", defaultValue: "")
  11. var expirationDate = StorageValue<Date?>(key: "expirationDate", defaultValue: nil)
  12. var sharedSecret = StorageValue<String>(key: "sharedSecret", defaultValue: "")
  13. var productionEnvironment = StorageValue<Bool>(key: "productionEnvironment", defaultValue: false)
  14. var remoteApnsKey = StorageValue<String>(key: "remoteApnsKey", defaultValue: "")
  15. var teamId = StorageValue<String?>(key: "teamId", defaultValue: nil)
  16. var remoteKeyId = StorageValue<String>(key: "remoteKeyId", defaultValue: "")
  17. var lfApnsKey = StorageValue<String>(key: "lfApnsKey", defaultValue: "")
  18. var lfKeyId = StorageValue<String>(key: "lfKeyId", defaultValue: "")
  19. var bundleId = StorageValue<String>(key: "bundleId", defaultValue: "")
  20. var user = StorageValue<String>(key: "user", defaultValue: "")
  21. var maxBolus = SecureStorageValue<HKQuantity>(key: "maxBolus", defaultValue: HKQuantity(unit: .internationalUnit(), doubleValue: 1.0))
  22. var maxCarbs = SecureStorageValue<HKQuantity>(key: "maxCarbs", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
  23. var maxProtein = SecureStorageValue<HKQuantity>(key: "maxProtein", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
  24. var maxFat = SecureStorageValue<HKQuantity>(key: "maxFat", defaultValue: HKQuantity(unit: .gram(), doubleValue: 30.0))
  25. var mealWithBolus = StorageValue<Bool>(key: "mealWithBolus", defaultValue: false)
  26. var mealWithFatProtein = StorageValue<Bool>(key: "mealWithFatProtein", defaultValue: false)
  27. // TODO: This flag can be deleted in March 2027. Check the commit for other places to cleanup.
  28. var hasSeenFatProteinOrderChange = StorageValue<Bool>(key: "hasSeenFatProteinOrderChange", defaultValue: true)
  29. var backgroundRefreshType = StorageValue<BackgroundRefreshType>(key: "backgroundRefreshType", defaultValue: .silentTune)
  30. var selectedBLEDevice = StorageValue<BLEDevice?>(key: "selectedBLEDevice", defaultValue: nil)
  31. var debugLogLevel = StorageValue<Bool>(key: "debugLogLevel", defaultValue: true)
  32. var contactTrend = StorageValue<ContactIncludeOption>(key: "contactTrend", defaultValue: .off)
  33. var contactDelta = StorageValue<ContactIncludeOption>(key: "contactDelta", defaultValue: .off)
  34. var contactIOB = StorageValue<ContactIncludeOption>(key: "contactIOB", defaultValue: .off)
  35. var contactTrendTarget = StorageValue<ContactType>(key: "contactTrendTarget", defaultValue: .BG)
  36. var contactDeltaTarget = StorageValue<ContactType>(key: "contactDeltaTarget", defaultValue: .BG)
  37. var contactIOBTarget = StorageValue<ContactType>(key: "contactIOBTarget", defaultValue: .BG)
  38. var contactEnabled = StorageValue<Bool>(key: "contactEnabled", defaultValue: false)
  39. var contactBackgroundColor = StorageValue<String>(key: "contactBackgroundColor", defaultValue: ContactColorOption.black.rawValue)
  40. var contactTextColor = StorageValue<String>(key: "contactTextColor", defaultValue: ContactColorOption.white.rawValue)
  41. var contactColorMode = StorageValue<ContactColorMode>(key: "contactColorMode", defaultValue: .staticColor)
  42. var sensorScheduleOffset = StorageValue<Double?>(key: "sensorScheduleOffset", defaultValue: nil)
  43. var alarms = StorageValue<[Alarm]>(key: "alarms", defaultValue: [])
  44. var alarmConfiguration = StorageValue<AlarmConfiguration>(key: "alarmConfiguration", defaultValue: .default)
  45. var lastOverrideStartNotified = StorageValue<TimeInterval?>(key: "lastOverrideStartNotified", defaultValue: nil)
  46. var lastOverrideEndNotified = StorageValue<TimeInterval?>(key: "lastOverrideEndNotified", defaultValue: nil)
  47. var lastTempTargetStartNotified = StorageValue<TimeInterval?>(key: "lastTempTargetStartNotified", defaultValue: nil)
  48. var lastTempTargetEndNotified = StorageValue<TimeInterval?>(key: "lastTempTargetEndNotified", defaultValue: nil)
  49. var lastRecBolusNotified = StorageValue<Double?>(key: "lastRecBolusNotified", defaultValue: nil)
  50. var lastCOBNotified = StorageValue<Double?>(key: "lastCOBNotified", defaultValue: nil)
  51. var lastMissedBolusNotified = StorageValue<Date?>(key: "lastMissedBolusNotified", defaultValue: nil)
  52. var pendingFutureCarbs = StorageValue<[PendingFutureCarb]>(key: "pendingFutureCarbs", defaultValue: [])
  53. // General Settings [BEGIN]
  54. var appBadge = StorageValue<Bool>(key: "appBadge", defaultValue: true)
  55. var colorBGText = StorageValue<Bool>(key: "colorBGText", defaultValue: true)
  56. var appearanceMode = StorageValue<AppearanceMode>(key: "appearanceMode", defaultValue: .dark)
  57. var showStats = StorageValue<Bool>(key: "showStats", defaultValue: true)
  58. var useIFCC = StorageValue<Bool>(key: "useIFCC", defaultValue: false)
  59. var showSmallGraph = StorageValue<Bool>(key: "showSmallGraph", defaultValue: true)
  60. var screenlockSwitchState = StorageValue<Bool>(key: "screenlockSwitchState", defaultValue: true)
  61. var showDisplayName = StorageValue<Bool>(key: "showDisplayName", defaultValue: false)
  62. var snoozerEmoji = StorageValue<Bool>(key: "snoozerEmoji", defaultValue: false)
  63. var forcePortraitMode = StorageValue<Bool>(key: "forcePortraitMode", defaultValue: false)
  64. var speakBG = StorageValue<Bool>(key: "speakBG", defaultValue: false)
  65. var speakBGAlways = StorageValue<Bool>(key: "speakBGAlways", defaultValue: true)
  66. var speakLowBG = StorageValue<Bool>(key: "speakLowBG", defaultValue: false)
  67. var speakProactiveLowBG = StorageValue<Bool>(key: "speakProactiveLowBG", defaultValue: false)
  68. var speakFastDropDelta = StorageValue<Double>(key: "speakFastDropDelta", defaultValue: 10.0)
  69. var speakLowBGLimit = StorageValue<Double>(key: "speakLowBGLimit", defaultValue: 72.0)
  70. var speakHighBGLimit = StorageValue<Double>(key: "speakHighBGLimit", defaultValue: 180.0)
  71. var speakHighBG = StorageValue<Bool>(key: "speakHighBG", defaultValue: false)
  72. var speakLanguage = StorageValue<String>(key: "speakLanguage", defaultValue: "en")
  73. // General Settings [END]
  74. // Live Activity glucose state
  75. var lastBgReadingTimeSeconds = StorageValue<TimeInterval?>(key: "lastBgReadingTimeSeconds", defaultValue: nil)
  76. var lastDeltaMgdl = StorageValue<Double?>(key: "lastDeltaMgdl", defaultValue: nil)
  77. var lastTrendCode = StorageValue<String?>(key: "lastTrendCode", defaultValue: nil)
  78. var lastIOB = StorageValue<Double?>(key: "lastIOB", defaultValue: nil)
  79. var lastCOB = StorageValue<Double?>(key: "lastCOB", defaultValue: nil)
  80. var projectedBgMgdl = StorageValue<Double?>(key: "projectedBgMgdl", defaultValue: nil)
  81. // Live Activity extended InfoType data
  82. var lastBasal = StorageValue<String>(key: "lastBasal", defaultValue: "")
  83. var lastPumpReservoirU = StorageValue<Double?>(key: "lastPumpReservoirU", defaultValue: nil)
  84. var lastAutosens = StorageValue<Double?>(key: "lastAutosens", defaultValue: nil)
  85. var lastTdd = StorageValue<Double?>(key: "lastTdd", defaultValue: nil)
  86. var lastTargetLowMgdl = StorageValue<Double?>(key: "lastTargetLowMgdl", defaultValue: nil)
  87. var lastTargetHighMgdl = StorageValue<Double?>(key: "lastTargetHighMgdl", defaultValue: nil)
  88. var lastIsfMgdlPerU = StorageValue<Double?>(key: "lastIsfMgdlPerU", defaultValue: nil)
  89. var lastCarbRatio = StorageValue<Double?>(key: "lastCarbRatio", defaultValue: nil)
  90. var lastCarbsToday = StorageValue<Double?>(key: "lastCarbsToday", defaultValue: nil)
  91. var lastProfileName = StorageValue<String>(key: "lastProfileName", defaultValue: "")
  92. var iageInsertTime = StorageValue<TimeInterval>(key: "iageInsertTime", defaultValue: 0)
  93. var lastMinBgMgdl = StorageValue<Double?>(key: "lastMinBgMgdl", defaultValue: nil)
  94. var lastMaxBgMgdl = StorageValue<Double?>(key: "lastMaxBgMgdl", defaultValue: nil)
  95. // Live Activity
  96. var laEnabled = StorageValue<Bool>(key: "laEnabled", defaultValue: false)
  97. var laRenewBy = StorageValue<TimeInterval>(key: "laRenewBy", defaultValue: 0)
  98. var laRenewalFailed = StorageValue<Bool>(key: "laRenewalFailed", defaultValue: false)
  99. var laPushToStartToken = StorageValue<String>(key: "laPushToStartToken", defaultValue: "")
  100. var laLastPushToStartAt = StorageValue<TimeInterval>(key: "laLastPushToStartAt", defaultValue: 0)
  101. var laPushToStartBackoff = StorageValue<TimeInterval>(key: "laPushToStartBackoff", defaultValue: 0)
  102. // Graph Settings [BEGIN]
  103. var showDots = StorageValue<Bool>(key: "showDots", defaultValue: true)
  104. var showLines = StorageValue<Bool>(key: "showLines", defaultValue: true)
  105. var showValues = StorageValue<Bool>(key: "showValues", defaultValue: true)
  106. var showAbsorption = StorageValue<Bool>(key: "showAbsorption", defaultValue: true)
  107. var showDIALines = StorageValue<Bool>(key: "showDIAMarkers", defaultValue: true)
  108. var show30MinLine = StorageValue<Bool>(key: "show30MinLine", defaultValue: false)
  109. var show90MinLine = StorageValue<Bool>(key: "show90MinLine", defaultValue: false)
  110. var showMidnightLines = StorageValue<Bool>(key: "showMidnightMarkers", defaultValue: false)
  111. var showYesterdayLine = StorageValue<Bool>(key: "showYesterdayLine", defaultValue: false)
  112. var smallGraphTreatments = StorageValue<Bool>(key: "smallGraphTreatments", defaultValue: true)
  113. var smallGraphHeight = StorageValue<Int>(key: "smallGraphHeight", defaultValue: 40)
  114. var predictionToLoad = StorageValue<Double>(key: "predictionToLoad", defaultValue: 1.0)
  115. var predictionDisplayType = StorageValue<PredictionDisplayType>(key: "predictionDisplayType", defaultValue: .cone)
  116. var minBasalScale = StorageValue<Double>(key: "minBasalScale", defaultValue: 5.0)
  117. var minBGScale = StorageValue<Double>(key: "minBGScale", defaultValue: 250.0)
  118. var lowLine = StorageValue<Double>(key: "lowLine", defaultValue: 70.0)
  119. var highLine = StorageValue<Double>(key: "highLine", defaultValue: 180.0)
  120. var downloadDays = StorageValue<Int>(key: "downloadDays", defaultValue: 1)
  121. var graphTimeZoneEnabled = StorageValue<Bool>(key: "graphTimeZoneEnabled", defaultValue: false)
  122. var graphTimeZoneIdentifier = StorageValue<String>(key: "graphTimeZoneIdentifier", defaultValue: TimeZone.current.identifier)
  123. // Graph Settings [END]
  124. // Calendar entries [BEGIN]
  125. var writeCalendarEvent = StorageValue<Bool>(key: "writeCalendarEvent", defaultValue: false)
  126. var calendarIdentifier = StorageValue<String>(key: "calendarIdentifier", defaultValue: "")
  127. var watchLine1 = StorageValue<String>(key: "watchLine1", defaultValue: "%BG% %DIRECTION% %DELTA% %MINAGO%")
  128. var watchLine2 = StorageValue<String>(key: "watchLine2", defaultValue: "C:%COB% I:%IOB% B:%BASAL%")
  129. // Calendar entries [END]
  130. // MARK: - Dexcom Share --------------------------------------------------------
  131. var shareUserName = StorageValue<String>(key: "shareUserName", defaultValue: "")
  132. var sharePassword = StorageValue<String>(key: "sharePassword", defaultValue: "")
  133. var shareServer = StorageValue<String>(key: "shareServer", defaultValue: "US")
  134. // MARK: - Graph ---------------------------------------------------------------
  135. var chartScaleX = StorageValue<Double>(key: "chartScaleX", defaultValue: 18.0)
  136. // MARK: - Advanced settings ---------------------------------------------------
  137. var downloadTreatments = StorageValue<Bool>(key: "downloadTreatments", defaultValue: true)
  138. var downloadPrediction = StorageValue<Bool>(key: "downloadPrediction", defaultValue: true)
  139. var graphOtherTreatments = StorageValue<Bool>(key: "graphOtherTreatments", defaultValue: true)
  140. var graphBasal = StorageValue<Bool>(key: "graphBasal", defaultValue: true)
  141. var graphBolus = StorageValue<Bool>(key: "graphBolus", defaultValue: true)
  142. var graphCarbs = StorageValue<Bool>(key: "graphCarbs", defaultValue: true)
  143. var bgUpdateDelay = StorageValue<Int>(key: "bgUpdateDelay", defaultValue: 10)
  144. // MARK: - Insert times (sensor / pump) ---------------------------------------
  145. var cageInsertTime = StorageValue<TimeInterval>(key: "cageInsertTime", defaultValue: 0)
  146. var sageInsertTime = StorageValue<TimeInterval>(key: "sageInsertTime", defaultValue: 0)
  147. // MARK: - Version-info ---------------------------
  148. var cachedForVersion = StorageValue<String?>(key: "cachedForVersion", defaultValue: nil)
  149. var latestVersion = StorageValue<String?>(key: "latestVersion", defaultValue: nil)
  150. var latestVersionChecked = StorageValue<Date?>(key: "latestVersionChecked", defaultValue: nil)
  151. var currentVersionBlackListed = StorageValue<Bool>(key: "currentVersionBlackListed", defaultValue: false)
  152. var lastBlacklistNotificationShown = StorageValue<Date?>(key: "lastBlacklistNotificationShown", defaultValue: nil)
  153. var lastVersionUpdateNotificationShown = StorageValue<Date?>(key: "lastVersionUpdateNotificationShown", defaultValue: nil)
  154. var lastExpirationNotificationShown = StorageValue<Date?>(key: "lastExpirationNotificationShown", defaultValue: nil)
  155. // MARK: - Telemetry -----------------------------------------------------------
  156. // See LoopFollow/Helpers/Telemetry.swift.
  157. var telemetryEnabled = StorageValue<Bool>(key: "telemetryEnabled", defaultValue: true)
  158. var telemetryConsentDecisionMade = StorageValue<Bool>(key: "telemetryConsentDecisionMade", defaultValue: false)
  159. var telemetryLastSentAt = StorageValue<Date?>(key: "telemetryLastSentAt", defaultValue: nil)
  160. var telemetryLastSentSha = StorageValue<String>(key: "telemetryLastSentSha", defaultValue: "")
  161. // Sliding 7-day window of cold-launch timestamps.
  162. var telemetryColdLaunchTimes = StorageValue<[Date]>(key: "telemetryColdLaunchTimes", defaultValue: [])
  163. var hideInfoTable = StorageValue<Bool>(key: "hideInfoTable", defaultValue: false)
  164. var token = StorageValue<String>(key: "token", defaultValue: "")
  165. var units = StorageValue<String>(key: "units", defaultValue: "mg/dL")
  166. var hasConfiguredUnits = StorageValue<Bool>(key: "hasConfiguredUnits", defaultValue: false)
  167. var infoDisplayItems = StorageValue<[InfoDisplayItem]>(
  168. key: "infoDisplayItems",
  169. defaultValue: InfoType.allCases.map {
  170. InfoDisplayItem(type: $0, isVisible: $0.defaultVisible, coloring: InfoColoring())
  171. }
  172. )
  173. var url = StorageValue<String>(key: "url", defaultValue: "")
  174. var device = StorageValue<String>(key: "device", defaultValue: "")
  175. var nsWriteAuth = StorageValue<Bool>(key: "nsWriteAuth", defaultValue: false)
  176. var nsAdminAuth = StorageValue<Bool>(key: "nsAdminAuth", defaultValue: false)
  177. var webSocketEnabled = StorageValue<Bool>(key: "webSocketEnabled", defaultValue: true)
  178. // When adding a new migration step in `runMigrationsIfNeeded()`, bump this default
  179. // to the new latest step number so fresh installs skip all migrations. Other defaults
  180. // in this file must reflect the post-migration final state for a fresh install.
  181. var migrationStep = StorageValue<Int>(key: "migrationStep", defaultValue: 10)
  182. var persistentNotification = StorageValue<Bool>(key: "persistentNotification", defaultValue: false)
  183. var persistentNotificationLastBGTime = StorageValue<Date>(key: "persistentNotificationLastBGTime", defaultValue: .distantPast)
  184. var lastLoopingChecked = StorageValue<Date?>(key: "lastLoopingChecked", defaultValue: nil)
  185. var lastBGChecked = StorageValue<Date?>(key: "lastBGChecked", defaultValue: nil)
  186. var lastLoopTime = StorageValue<TimeInterval>(key: "lastLoopTime", defaultValue: 0)
  187. // Tab positions - which position each item is in (positions 1-4 are customizable, 5 is always Menu)
  188. var homePosition = StorageValue<TabPosition>(key: "homePosition", defaultValue: .position1)
  189. var alarmsPosition = StorageValue<TabPosition>(key: "alarmsPosition", defaultValue: .position2)
  190. var snoozerPosition = StorageValue<TabPosition>(key: "snoozerPosition", defaultValue: .position3)
  191. var nightscoutPosition = StorageValue<TabPosition>(key: "nightscoutPosition", defaultValue: .position4)
  192. var remotePosition = StorageValue<TabPosition>(key: "remotePosition", defaultValue: .menu)
  193. var statisticsPosition = StorageValue<TabPosition>(key: "statisticsPosition", defaultValue: .menu)
  194. var treatmentsPosition = StorageValue<TabPosition>(key: "treatmentsPosition", defaultValue: .menu)
  195. var loopAPNSQrCodeURL = StorageValue<String>(key: "loopAPNSQrCodeURL", defaultValue: "")
  196. var bolusIncrement = SecureStorageValue<HKQuantity>(key: "bolusIncrement", defaultValue: HKQuantity(unit: .internationalUnit(), doubleValue: 0.05))
  197. var bolusIncrementDetected = StorageValue<Bool>(key: "bolusIncrementDetected", defaultValue: false)
  198. var remoteBolusHistory = StorageValue<[RemoteBolusHistoryEntry]>(key: "remoteBolusHistory", defaultValue: [])
  199. var remoteMealHistory = StorageValue<[RemoteMealHistoryEntry]>(key: "remoteMealHistory", defaultValue: [])
  200. // Statistics display preferences
  201. var showGMI = StorageValue<Bool>(key: "showGMI", defaultValue: true)
  202. var showStdDev = StorageValue<Bool>(key: "showStdDev", defaultValue: true)
  203. var showTITR = StorageValue<Bool>(key: "showTITR", defaultValue: false)
  204. var timeInRangeModeRaw = StorageValue<String>(key: "timeInRangeMode", defaultValue: "TIR")
  205. static let shared = Storage()
  206. private init() {}
  207. // MARK: - Tab Position Helpers
  208. /// Get the position for a given tab item
  209. func position(for item: TabItem) -> TabPosition {
  210. switch item {
  211. case .home: homePosition.value
  212. case .alarms: alarmsPosition.value
  213. case .remote: remotePosition.value
  214. case .nightscout: nightscoutPosition.value
  215. case .snoozer: snoozerPosition.value
  216. case .stats: statisticsPosition.value
  217. case .treatments: treatmentsPosition.value
  218. }
  219. }
  220. /// Set the position for a given tab item
  221. func setPosition(_ position: TabPosition, for item: TabItem) {
  222. switch item {
  223. case .home: homePosition.value = position
  224. case .alarms: alarmsPosition.value = position
  225. case .remote: remotePosition.value = position
  226. case .nightscout: nightscoutPosition.value = position
  227. case .snoozer: snoozerPosition.value = position
  228. case .stats: statisticsPosition.value = position
  229. case .treatments: treatmentsPosition.value = position
  230. }
  231. }
  232. /// Get the tab item at a specific position (nil if no item at that position)
  233. func tabItem(at position: TabPosition) -> TabItem? {
  234. for item in TabItem.allCases {
  235. // Use normalized comparison to handle legacy values (.more, .disabled -> .menu)
  236. if self.position(for: item).normalized == position.normalized {
  237. return item
  238. }
  239. }
  240. return nil
  241. }
  242. /// Get all items in the Menu (position 5)
  243. func itemsInMenu() -> [TabItem] {
  244. TabItem.featureOrder.filter { position(for: $0).normalized == .menu }
  245. }
  246. /// Get items ordered by their position in the tab bar (positions 1-4)
  247. func orderedTabBarItems() -> [TabItem] {
  248. TabPosition.customizablePositions.compactMap { tabItem(at: $0) }
  249. }
  250. }