CGMStateModel.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import CGMBLEKit
  2. import Combine
  3. import G7SensorKit
  4. import LoopKitUI
  5. import SwiftUI
  6. struct cgmName: Identifiable, Hashable {
  7. var id: String
  8. var type: CGMType
  9. var displayName: String
  10. var subtitle: String
  11. }
  12. let cgmDefaultName = cgmName(
  13. id: CGMType.none.id,
  14. type: .none,
  15. displayName: CGMType.none.displayName,
  16. subtitle: CGMType.none.subtitle
  17. )
  18. extension CGM {
  19. final class StateModel: BaseStateModel<Provider> {
  20. @Injected() var cgmManager: FetchGlucoseManager!
  21. @Injected() var calendarManager: CalendarManager!
  22. @Injected() var pluginCGMManager: PluginManager!
  23. @Injected() private var broadcaster: Broadcaster!
  24. @Injected() var nightscoutManager: NightscoutManager!
  25. @Published var setupCGM: Bool = false
  26. @Published var cgmCurrent = cgmDefaultName
  27. @Published var smoothGlucose = false
  28. @Published var createCalendarEvents = false
  29. @Published var calendarIDs: [String] = []
  30. @Published var currentCalendarID: String = ""
  31. @Persisted(key: "CalendarManager.currentCalendarID") var storedCalendarID: String? = nil
  32. @Published var cgmTransmitterDeviceAddress: String? = nil
  33. @Published var listOfCGM: [cgmName] = []
  34. @Published var url: URL?
  35. override func subscribe() {
  36. // collect the list of CGM available with plugins and CGMType defined manually
  37. listOfCGM = CGMType.allCases.filter { $0 != CGMType.plugin }.map {
  38. cgmName(id: $0.id, type: $0, displayName: $0.displayName, subtitle: $0.subtitle)
  39. } +
  40. pluginCGMManager.availableCGMManagers.map {
  41. cgmName(id: $0.identifier, type: CGMType.plugin, displayName: $0.localizedTitle, subtitle: $0.localizedTitle)
  42. }
  43. switch settingsManager.settings.cgm {
  44. case .plugin:
  45. if let cgmPluginInfo = listOfCGM.first(where: { $0.id == settingsManager.settings.cgmPluginIdentifier }) {
  46. cgmCurrent = cgmName(
  47. id: settingsManager.settings.cgmPluginIdentifier,
  48. type: .plugin,
  49. displayName: cgmPluginInfo.displayName,
  50. subtitle: cgmPluginInfo.subtitle
  51. )
  52. } else {
  53. // no more type of plugin available - restart to defaut
  54. cgmCurrent = cgmDefaultName
  55. }
  56. default:
  57. cgmCurrent = cgmName(
  58. id: settingsManager.settings.cgm.id,
  59. type: settingsManager.settings.cgm,
  60. displayName: settingsManager.settings.cgm.displayName,
  61. subtitle: settingsManager.settings.cgm.subtitle
  62. )
  63. }
  64. url = nightscoutManager.cgmURL
  65. switch url?.absoluteString {
  66. case "http://127.0.0.1:1979":
  67. url = URL(string: "spikeapp://")!
  68. case "http://127.0.0.1:17580":
  69. url = URL(string: "diabox://")!
  70. // case CGMType.libreTransmitter.appURL?.absoluteString:
  71. // showModal(for: .libreConfig)
  72. default: break
  73. }
  74. currentCalendarID = storedCalendarID ?? ""
  75. calendarIDs = calendarManager.calendarIDs()
  76. cgmTransmitterDeviceAddress = UserDefaults.standard.cgmTransmitterDeviceAddress
  77. subscribeSetting(\.useCalendar, on: $createCalendarEvents) { createCalendarEvents = $0 }
  78. subscribeSetting(\.smoothGlucose, on: $smoothGlucose, initial: { smoothGlucose = $0 })
  79. $cgmCurrent
  80. .removeDuplicates()
  81. .sink { [weak self] value in
  82. guard let self = self else { return }
  83. guard self.cgmManager.cgmGlucoseSourceType != nil else {
  84. self.settingsManager.settings.cgm = .nightscout
  85. return
  86. }
  87. if value.type != self.settingsManager.settings.cgm ||
  88. value.id != self.settingsManager.settings.cgmPluginIdentifier
  89. {
  90. self.settingsManager.settings.cgm = value.type
  91. self.settingsManager.settings.cgmPluginIdentifier = value.id
  92. self.cgmManager.updateGlucoseSource(
  93. cgmGlucoseSourceType: value.type,
  94. cgmGlucosePluginId: value.id
  95. )
  96. }
  97. }
  98. .store(in: &lifetime)
  99. $createCalendarEvents
  100. .removeDuplicates()
  101. .flatMap { [weak self] ok -> AnyPublisher<Bool, Never> in
  102. guard ok, let self = self else { return Just(false).eraseToAnyPublisher() }
  103. return self.calendarManager.requestAccessIfNeeded()
  104. }
  105. .map { [weak self] ok -> [String] in
  106. guard ok, let self = self else { return [] }
  107. return self.calendarManager.calendarIDs()
  108. }
  109. .receive(on: DispatchQueue.main)
  110. .weakAssign(to: \.calendarIDs, on: self)
  111. .store(in: &lifetime)
  112. $currentCalendarID
  113. .removeDuplicates()
  114. .sink { [weak self] id in
  115. guard id.isNotEmpty else {
  116. self?.calendarManager.currentCalendarID = nil
  117. return
  118. }
  119. self?.calendarManager.currentCalendarID = id
  120. }
  121. .store(in: &lifetime)
  122. }
  123. }
  124. }
  125. extension CGM.StateModel: CompletionDelegate {
  126. func completionNotifyingDidComplete(_: CompletionNotifying) {
  127. setupCGM = false
  128. // if CGM was deleted
  129. if cgmManager.cgmGlucoseSourceType == nil {
  130. cgmCurrent = cgmDefaultName
  131. settingsManager.settings.cgm = cgmDefaultName.type
  132. settingsManager.settings.cgmPluginIdentifier = cgmDefaultName.id
  133. cgmManager.deleteGlucoseSource()
  134. } else {
  135. cgmManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id)
  136. }
  137. // refresh the upload options
  138. settingsManager.settings.uploadGlucose = cgmManager.shouldSyncToRemoteService
  139. // update if required the Glucose source
  140. DispatchQueue.main.async {
  141. self.broadcaster.notify(GlucoseObserver.self, on: .main) {
  142. $0.glucoseDidUpdate([])
  143. }
  144. }
  145. }
  146. }
  147. extension CGM.StateModel: CGMManagerOnboardingDelegate {
  148. func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
  149. // update the setting of upload Glucose in services
  150. settingsManager.settings.uploadGlucose = cgmManager.shouldSyncToRemoteService
  151. // update the glucose source
  152. cgmManager.updateGlucoseSource(
  153. cgmGlucoseSourceType: cgmCurrent.type,
  154. cgmGlucosePluginId: cgmCurrent.id,
  155. newManager: manager
  156. )
  157. }
  158. func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
  159. // nothing to do ?
  160. }
  161. }