ProfileManager.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // ProfileManager.swift
  2. // LoopFollow
  3. // Created by Jonas Björkert on 2024-07-12.
  4. // Copyright © 2024 Jon Fawcett. All rights reserved.
  5. import Foundation
  6. import HealthKit
  7. final class ProfileManager {
  8. // MARK: - Singleton Instance
  9. static let shared = ProfileManager()
  10. // MARK: - Properties
  11. var isfSchedule: [TimeValue<HKQuantity>]
  12. var basalSchedule: [TimeValue<Double>]
  13. var carbRatioSchedule: [TimeValue<Double>]
  14. var targetLowSchedule: [TimeValue<HKQuantity>]
  15. var targetHighSchedule: [TimeValue<HKQuantity>]
  16. var overrides: [Override]
  17. var trioOverrides: [TrioOverride]
  18. var units: HKUnit
  19. var timezone: String
  20. var defaultProfile: String
  21. // MARK: - Nested Structures
  22. struct TimeValue<T> {
  23. let timeAsSeconds: Int
  24. let value: T
  25. }
  26. struct Override {
  27. let name: String
  28. let targetRange: [HKQuantity]
  29. let duration: Int
  30. let insulinNeedsScaleFactor: Double
  31. let symbol: String
  32. }
  33. struct TrioOverride {
  34. let name: String
  35. let duration: Double?
  36. let percentage: Double?
  37. let target: HKQuantity?
  38. }
  39. // MARK: - Initializer
  40. private init() {
  41. self.isfSchedule = []
  42. self.basalSchedule = []
  43. self.carbRatioSchedule = []
  44. self.targetLowSchedule = []
  45. self.targetHighSchedule = []
  46. self.overrides = []
  47. self.trioOverrides = []
  48. self.units = .millimolesPerLiter
  49. self.timezone = "UTC"
  50. self.defaultProfile = ""
  51. }
  52. // MARK: - Methods
  53. func loadProfile(from profileData: NSProfile) {
  54. guard let store = profileData.store["default"] ?? profileData.store["Default"] else {
  55. return
  56. }
  57. self.units = profileData.units.lowercased() == "mg/dl" ? .milligramsPerDeciliter : .millimolesPerLiter
  58. self.timezone = store.timezone
  59. self.defaultProfile = profileData.defaultProfile
  60. self.isfSchedule = store.sens.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: HKQuantity(unit: self.units, doubleValue: $0.value)) }
  61. self.basalSchedule = store.basal.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: $0.value) }
  62. self.carbRatioSchedule = store.carbratio.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: $0.value) }
  63. self.targetLowSchedule = store.target_low?.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: HKQuantity(unit: self.units, doubleValue: $0.value)) } ?? []
  64. self.targetHighSchedule = store.target_high?.map { TimeValue(timeAsSeconds: Int($0.timeAsSeconds), value: HKQuantity(unit: self.units, doubleValue: $0.value)) } ?? []
  65. if let overrides = store.overrides {
  66. self.overrides = overrides.map { Override(
  67. name: $0.name ?? "",
  68. targetRange: $0.targetRange?.map { HKQuantity(unit: self.units, doubleValue: $0) } ?? [],
  69. duration: $0.duration ?? 0,
  70. insulinNeedsScaleFactor: $0.insulinNeedsScaleFactor ?? 1.0,
  71. symbol: $0.symbol ?? ""
  72. )}
  73. } else {
  74. self.overrides = []
  75. }
  76. if let trioOverrides = profileData.trioOverrides {
  77. self.trioOverrides = trioOverrides.map { entry in
  78. let targetQuantity = entry.target != nil ? HKQuantity(unit: self.units, doubleValue: entry.target!) : nil
  79. return TrioOverride(
  80. name: entry.name,
  81. duration: entry.duration,
  82. percentage: entry.percentage,
  83. target: targetQuantity
  84. )
  85. }
  86. } else {
  87. self.trioOverrides = []
  88. }
  89. Storage.shared.deviceToken.value = profileData.deviceToken ?? ""
  90. Storage.shared.bundleId.value = profileData.bundleIdentifier ?? ""
  91. Storage.shared.productionEnvironment.value = profileData.isAPNSProduction ?? false
  92. Storage.shared.teamId.value = profileData.teamID ?? Storage.shared.teamId.value ?? ""
  93. }
  94. func currentISF() -> HKQuantity? {
  95. return getCurrentValue(from: isfSchedule)
  96. }
  97. func currentBasal() -> String? {
  98. if let basal = getCurrentValue(from: basalSchedule) {
  99. return Localizer.formatToLocalizedString(basal, maxFractionDigits: 2, minFractionDigits: 0)
  100. }
  101. return nil
  102. }
  103. func currentCarbRatio() -> Double? {
  104. return getCurrentValue(from: carbRatioSchedule)
  105. }
  106. func currentTargetLow() -> HKQuantity? {
  107. return getCurrentValue(from: targetLowSchedule)
  108. }
  109. func currentTargetHigh() -> HKQuantity? {
  110. return getCurrentValue(from: targetHighSchedule)
  111. }
  112. private func getCurrentValue<T>(from schedule: [TimeValue<T>]) -> T? {
  113. guard !schedule.isEmpty else { return nil }
  114. let now = Date()
  115. let calendar = Calendar.current
  116. let currentTimeInSeconds = calendar.component(.hour, from: now) * 3600 +
  117. calendar.component(.minute, from: now) * 60 +
  118. calendar.component(.second, from: now)
  119. var lastValue: T?
  120. for timeValue in schedule {
  121. if currentTimeInSeconds >= timeValue.timeAsSeconds {
  122. lastValue = timeValue.value
  123. } else {
  124. break
  125. }
  126. }
  127. return lastValue
  128. }
  129. func clear() {
  130. self.isfSchedule = []
  131. self.basalSchedule = []
  132. self.carbRatioSchedule = []
  133. self.targetLowSchedule = []
  134. self.targetHighSchedule = []
  135. self.overrides = []
  136. self.units = .millimolesPerLiter
  137. self.timezone = "UTC"
  138. self.defaultProfile = ""
  139. }
  140. }