StorageCurrentGlucoseStateProvider.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // LoopFollow
  2. // StorageCurrentGlucoseStateProvider.swift
  3. import Foundation
  4. import HealthKit
  5. /// Reads the latest glucose state from LoopFollow's Storage and Observable layers.
  6. /// This is the only file in the pipeline that is allowed to touch Storage.shared
  7. /// or Observable.shared — all other layers read exclusively from this provider.
  8. struct StorageCurrentGlucoseStateProvider: CurrentGlucoseStateProviding {
  9. // MARK: - Core Glucose
  10. var glucoseMgdl: Double? {
  11. guard let bg = Observable.shared.bg.value, bg > 0 else { return nil }
  12. return Double(bg)
  13. }
  14. var deltaMgdl: Double? {
  15. Storage.shared.lastDeltaMgdl.value
  16. }
  17. var projectedMgdl: Double? {
  18. Storage.shared.projectedBgMgdl.value
  19. }
  20. var updatedAt: Date? {
  21. guard let t = Storage.shared.lastBgReadingTimeSeconds.value else { return nil }
  22. return Date(timeIntervalSince1970: t)
  23. }
  24. var trendCode: String? {
  25. Storage.shared.lastTrendCode.value
  26. }
  27. // MARK: - Secondary Metrics
  28. var iob: Double? {
  29. Storage.shared.lastIOB.value
  30. }
  31. var cob: Double? {
  32. Storage.shared.lastCOB.value
  33. }
  34. // MARK: - Extended Metrics
  35. var override: String? {
  36. Observable.shared.override.value
  37. }
  38. var overrideEndAt: TimeInterval? {
  39. Observable.shared.overrideEndAt.value
  40. }
  41. var tempTargetMgdl: Double? {
  42. Observable.shared.tempTarget.value?.doubleValue(for: .milligramsPerDeciliter)
  43. }
  44. var tempTargetEndAt: TimeInterval? {
  45. Observable.shared.tempTargetEndAt.value
  46. }
  47. var recBolus: Double? {
  48. Observable.shared.deviceRecBolus.value
  49. }
  50. var battery: Double? {
  51. Observable.shared.deviceBatteryLevel.value
  52. }
  53. var pumpBattery: Double? {
  54. Observable.shared.pumpBatteryLevel.value
  55. }
  56. var basalRate: String {
  57. Storage.shared.lastBasal.value
  58. }
  59. var pumpReservoirU: Double? {
  60. Storage.shared.lastPumpReservoirU.value
  61. }
  62. var autosens: Double? {
  63. Storage.shared.lastAutosens.value
  64. }
  65. var tdd: Double? {
  66. Storage.shared.lastTdd.value
  67. }
  68. var targetLowMgdl: Double? {
  69. Storage.shared.lastTargetLowMgdl.value
  70. }
  71. var targetHighMgdl: Double? {
  72. Storage.shared.lastTargetHighMgdl.value
  73. }
  74. var isfMgdlPerU: Double? {
  75. Storage.shared.lastIsfMgdlPerU.value
  76. }
  77. var carbRatio: Double? {
  78. Storage.shared.lastCarbRatio.value
  79. }
  80. var carbsToday: Double? {
  81. Storage.shared.lastCarbsToday.value
  82. }
  83. var profileName: String? {
  84. let raw = Storage.shared.lastProfileName.value
  85. return raw.isEmpty ? nil : raw
  86. }
  87. var sageInsertTime: TimeInterval {
  88. Storage.shared.sageInsertTime.value
  89. }
  90. var cageInsertTime: TimeInterval {
  91. Storage.shared.cageInsertTime.value
  92. }
  93. var iageInsertTime: TimeInterval {
  94. Storage.shared.iageInsertTime.value
  95. }
  96. var minBgMgdl: Double? {
  97. Storage.shared.lastMinBgMgdl.value
  98. }
  99. var maxBgMgdl: Double? {
  100. Storage.shared.lastMaxBgMgdl.value
  101. }
  102. // MARK: - Loop Status
  103. var isNotLooping: Bool {
  104. let lastLoopTime = Storage.shared.lastLoopTime.value
  105. guard lastLoopTime > 0, !Storage.shared.url.value.isEmpty else { return false }
  106. return Date().timeIntervalSince1970 - lastLoopTime >= 15 * 60
  107. }
  108. // MARK: - Renewal
  109. var showRenewalOverlay: Bool {
  110. #if targetEnvironment(macCatalyst)
  111. return false
  112. #else
  113. // Push-to-start renews silently at the deadline, so only show the
  114. // "tap to update" overlay once renewal has actually failed (no token,
  115. // bad creds, rate-limited) — that is genuinely user-actionable.
  116. return Storage.shared.laRenewalFailed.value
  117. #endif
  118. }
  119. }