| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- // LoopFollow
- // StorageCurrentGlucoseStateProvider.swift
- import Foundation
- /// Reads the latest glucose state from LoopFollow's Storage and Observable layers.
- /// This is the only file in the pipeline that is allowed to touch Storage.shared
- /// or Observable.shared — all other layers read exclusively from this provider.
- struct StorageCurrentGlucoseStateProvider: CurrentGlucoseStateProviding {
- // MARK: - Core Glucose
- var glucoseMgdl: Double? {
- guard let bg = Observable.shared.bg.value, bg > 0 else { return nil }
- return Double(bg)
- }
- var deltaMgdl: Double? {
- Storage.shared.lastDeltaMgdl.value
- }
- var projectedMgdl: Double? {
- Storage.shared.projectedBgMgdl.value
- }
- var updatedAt: Date? {
- guard let t = Storage.shared.lastBgReadingTimeSeconds.value else { return nil }
- return Date(timeIntervalSince1970: t)
- }
- var trendCode: String? {
- Storage.shared.lastTrendCode.value
- }
- // MARK: - Secondary Metrics
- var iob: Double? {
- Storage.shared.lastIOB.value
- }
- var cob: Double? {
- Storage.shared.lastCOB.value
- }
- // MARK: - Extended Metrics
- var override: String? {
- Observable.shared.override.value
- }
- var recBolus: Double? {
- Observable.shared.deviceRecBolus.value
- }
- var battery: Double? {
- Observable.shared.deviceBatteryLevel.value
- }
- var pumpBattery: Double? {
- Observable.shared.pumpBatteryLevel.value
- }
- var basalRate: String {
- Storage.shared.lastBasal.value
- }
- var pumpReservoirU: Double? {
- Storage.shared.lastPumpReservoirU.value
- }
- var autosens: Double? {
- Storage.shared.lastAutosens.value
- }
- var tdd: Double? {
- Storage.shared.lastTdd.value
- }
- var targetLowMgdl: Double? {
- Storage.shared.lastTargetLowMgdl.value
- }
- var targetHighMgdl: Double? {
- Storage.shared.lastTargetHighMgdl.value
- }
- var isfMgdlPerU: Double? {
- Storage.shared.lastIsfMgdlPerU.value
- }
- var carbRatio: Double? {
- Storage.shared.lastCarbRatio.value
- }
- var carbsToday: Double? {
- Storage.shared.lastCarbsToday.value
- }
- var profileName: String? {
- let raw = Storage.shared.lastProfileName.value
- return raw.isEmpty ? nil : raw
- }
- var sageInsertTime: TimeInterval {
- Storage.shared.sageInsertTime.value
- }
- var cageInsertTime: TimeInterval {
- Storage.shared.cageInsertTime.value
- }
- var iageInsertTime: TimeInterval {
- Storage.shared.iageInsertTime.value
- }
- var minBgMgdl: Double? {
- Storage.shared.lastMinBgMgdl.value
- }
- var maxBgMgdl: Double? {
- Storage.shared.lastMaxBgMgdl.value
- }
- // MARK: - Loop Status
- var isNotLooping: Bool {
- let lastLoopTime = Storage.shared.lastLoopTime.value
- guard lastLoopTime > 0, !Storage.shared.url.value.isEmpty else { return false }
- return Date().timeIntervalSince1970 - lastLoopTime >= 15 * 60
- }
- // MARK: - Renewal
- var showRenewalOverlay: Bool {
- #if targetEnvironment(macCatalyst)
- return false
- #else
- // iOS 17.2+ renews silently via push-to-start at the deadline, so the
- // pre-emptive 30-minute "tap to update" overlay would be misleading
- // during normal operation. Only show it once renewal has actually
- // failed (no token, bad creds, rate-limited) — that is genuinely
- // user-actionable. iOS 16.x keeps the time-based warning because
- // renewal there requires the user to foreground the app.
- if #available(iOS 17.2, *) {
- return Storage.shared.laRenewalFailed.value
- } else {
- let renewBy = Storage.shared.laRenewBy.value
- let now = Date().timeIntervalSince1970
- return renewBy > 0 && now >= renewBy - LiveActivityManager.renewalWarning
- }
- #endif
- }
- }
|