G7SettingsViewModel.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. //
  2. // G7SettingsViewModel.swift
  3. // CGMBLEKitUI
  4. //
  5. // Created by Pete Schwamb on 10/4/22.
  6. // Copyright © 2022 LoopKit Authors. All rights reserved.
  7. //
  8. import Foundation
  9. import G7SensorKit
  10. import LoopKit
  11. import LoopKitUI
  12. import HealthKit
  13. public enum ColorStyle {
  14. case glucose, warning, critical, normal, dimmed
  15. }
  16. class G7SettingsViewModel: ObservableObject {
  17. @Published private(set) var scanning: Bool = false
  18. @Published private(set) var connected: Bool = false
  19. @Published private(set) var sensorName: String?
  20. @Published private(set) var activatedAt: Date?
  21. @Published private(set) var lastConnect: Date?
  22. @Published private(set) var latestReadingTimestamp: Date?
  23. @Published var uploadReadings: Bool = false {
  24. didSet {
  25. cgmManager.uploadReadings = uploadReadings
  26. }
  27. }
  28. let displayGlucosePreference: DisplayGlucosePreference
  29. private var lastReading: G7GlucoseMessage?
  30. lazy var dateFormatter: DateFormatter = {
  31. let formatter = DateFormatter()
  32. formatter.dateStyle = .short
  33. formatter.timeStyle = .short
  34. return formatter
  35. }()
  36. private var cgmManager: G7CGMManager
  37. var progressBarState: G7ProgressBarState {
  38. switch cgmManager.lifecycleState {
  39. case .searching:
  40. return .searchingForSensor
  41. case .ok:
  42. return .lifetimeRemaining
  43. case .warmup:
  44. return .warmupProgress
  45. case .failed:
  46. return .sensorFailed
  47. case .gracePeriod:
  48. return .gracePeriodRemaining
  49. case .expired:
  50. return .sensorExpired
  51. }
  52. }
  53. init(cgmManager: G7CGMManager, displayGlucosePreference: DisplayGlucosePreference) {
  54. self.cgmManager = cgmManager
  55. self.displayGlucosePreference = displayGlucosePreference
  56. updateValues()
  57. self.cgmManager.addStateObserver(self, queue: DispatchQueue.main)
  58. }
  59. func updateValues() {
  60. scanning = cgmManager.isScanning
  61. sensorName = cgmManager.sensorName
  62. activatedAt = cgmManager.sensorActivatedAt
  63. connected = cgmManager.isConnected
  64. lastConnect = cgmManager.lastConnect
  65. lastReading = cgmManager.latestReading
  66. latestReadingTimestamp = cgmManager.latestReadingTimestamp
  67. uploadReadings = cgmManager.state.uploadReadings
  68. }
  69. var progressBarColorStyle: ColorStyle {
  70. switch progressBarState {
  71. case .warmupProgress:
  72. return .glucose
  73. case .searchingForSensor:
  74. return .dimmed
  75. case .sensorExpired, .sensorFailed:
  76. return .critical
  77. case .lifetimeRemaining:
  78. guard let remaining = progressValue else {
  79. return .dimmed
  80. }
  81. if remaining > .hours(24) {
  82. return .glucose
  83. } else {
  84. return .warning
  85. }
  86. case .gracePeriodRemaining:
  87. return .critical
  88. }
  89. }
  90. var progressBarProgress: Double {
  91. switch progressBarState {
  92. case .searchingForSensor:
  93. return 0
  94. case .warmupProgress:
  95. guard let value = progressValue, value > 0 else {
  96. return 0
  97. }
  98. return 1 - value / G7Sensor.warmupDuration
  99. case .lifetimeRemaining:
  100. guard let value = progressValue, value > 0 else {
  101. return 0
  102. }
  103. return 1 - value / G7Sensor.lifetime
  104. case .gracePeriodRemaining:
  105. guard let value = progressValue, value > 0 else {
  106. return 0
  107. }
  108. return 1 - value / G7Sensor.gracePeriod
  109. case .sensorExpired, .sensorFailed:
  110. return 1
  111. }
  112. }
  113. var progressReferenceDate: Date? {
  114. switch progressBarState {
  115. case .searchingForSensor:
  116. return nil
  117. case .sensorExpired, .gracePeriodRemaining:
  118. return cgmManager.sensorEndsAt
  119. case .warmupProgress:
  120. return cgmManager.sensorFinishesWarmupAt
  121. case .lifetimeRemaining:
  122. return cgmManager.sensorExpiresAt
  123. case .sensorFailed:
  124. return nil
  125. }
  126. }
  127. var progressValue: TimeInterval? {
  128. switch progressBarState {
  129. case .sensorExpired, .sensorFailed, .searchingForSensor:
  130. guard let sensorEndsAt = cgmManager.sensorEndsAt else {
  131. return nil
  132. }
  133. return sensorEndsAt.timeIntervalSinceNow
  134. case .warmupProgress:
  135. guard let warmupFinishedAt = cgmManager.sensorFinishesWarmupAt else {
  136. return nil
  137. }
  138. return max(0, warmupFinishedAt.timeIntervalSinceNow)
  139. case .lifetimeRemaining:
  140. guard let expiration = cgmManager.sensorExpiresAt else {
  141. return nil
  142. }
  143. return max(0, expiration.timeIntervalSinceNow)
  144. case .gracePeriodRemaining:
  145. guard let sensorEndsAt = cgmManager.sensorEndsAt else {
  146. return nil
  147. }
  148. return max(0, sensorEndsAt.timeIntervalSinceNow)
  149. }
  150. }
  151. func scanForNewSensor() {
  152. cgmManager.scanForNewSensor()
  153. }
  154. var lastGlucoseString: String {
  155. guard let lastReading = lastReading, lastReading.hasReliableGlucose, let quantity = lastReading.glucoseQuantity else {
  156. return LocalizedString("– – –", comment: "No glucose value representation (3 dashes for mg/dL)")
  157. }
  158. switch lastReading.glucoseRangeCategory {
  159. case .some(.belowRange):
  160. return LocalizedString("LOW", comment: "String displayed instead of a glucose value below the CGM range")
  161. case .some(.aboveRange):
  162. return LocalizedString("HIGH", comment: "String displayed instead of a glucose value above the CGM range")
  163. default:
  164. return displayGlucosePreference.formatter.string(from: quantity)!
  165. }
  166. }
  167. var lastGlucoseTrendString: String {
  168. if let lastReading = lastReading, lastReading.hasReliableGlucose, let trendRate = lastReading.trendRate {
  169. return displayGlucosePreference.minuteRateFormatter.string(from: trendRate)!
  170. } else {
  171. return ""
  172. }
  173. }
  174. }
  175. extension G7SettingsViewModel: G7StateObserver {
  176. func g7StateDidUpdate(_ state: G7CGMManagerState?) {
  177. updateValues()
  178. }
  179. func g7ConnectionStatusDidChange() {
  180. updateValues()
  181. }
  182. }