CGMStateModel.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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 = .none
  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. self.setupCGM = false
  97. }
  98. }
  99. .store(in: &lifetime)
  100. $createCalendarEvents
  101. .removeDuplicates()
  102. .flatMap { [weak self] ok -> AnyPublisher<Bool, Never> in
  103. guard ok, let self = self else { return Just(false).eraseToAnyPublisher() }
  104. return self.calendarManager.requestAccessIfNeeded()
  105. }
  106. .map { [weak self] ok -> [String] in
  107. guard ok, let self = self else { return [] }
  108. return self.calendarManager.calendarIDs()
  109. }
  110. .receive(on: DispatchQueue.main)
  111. .weakAssign(to: \.calendarIDs, on: self)
  112. .store(in: &lifetime)
  113. $currentCalendarID
  114. .removeDuplicates()
  115. .sink { [weak self] id in
  116. guard id.isNotEmpty else {
  117. self?.calendarManager.currentCalendarID = nil
  118. return
  119. }
  120. self?.calendarManager.currentCalendarID = id
  121. }
  122. .store(in: &lifetime)
  123. }
  124. func displayNameOfApp() -> String? {
  125. guard cgmManager != nil else { return nil }
  126. var nameOfApp = "Open Application"
  127. switch cgmManager.cgmGlucoseSourceType {
  128. case .plugin:
  129. nameOfApp = "Open " + (cgmManager.cgmManager?.localizedTitle ?? "Application")
  130. default:
  131. nameOfApp = "Open " + cgmManager.cgmGlucoseSourceType.displayName
  132. }
  133. return nameOfApp
  134. }
  135. func urlOfApp() -> URL? {
  136. guard cgmManager != nil else { return nil }
  137. switch cgmManager.cgmGlucoseSourceType {
  138. case .plugin:
  139. return cgmManager.cgmManager?.appURL
  140. default:
  141. return cgmManager.cgmGlucoseSourceType.appURL
  142. }
  143. }
  144. }
  145. }
  146. extension CGM.StateModel: CompletionDelegate {
  147. func completionNotifyingDidComplete(_: CompletionNotifying) {
  148. setupCGM = false
  149. // if CGM was deleted
  150. if cgmManager.cgmGlucoseSourceType == nil {
  151. cgmCurrent = cgmDefaultName
  152. settingsManager.settings.cgm = cgmDefaultName.type
  153. settingsManager.settings.cgmPluginIdentifier = cgmDefaultName.id
  154. cgmManager.deleteGlucoseSource()
  155. } else {
  156. cgmManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id)
  157. }
  158. // update if required the Glucose source
  159. DispatchQueue.main.async {
  160. self.broadcaster.notify(GlucoseObserver.self, on: .main) {
  161. $0.glucoseDidUpdate([])
  162. }
  163. }
  164. }
  165. }
  166. extension CGM.StateModel: CGMManagerOnboardingDelegate {
  167. func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
  168. // update the glucose source
  169. cgmManager.updateGlucoseSource(
  170. cgmGlucoseSourceType: cgmCurrent.type,
  171. cgmGlucosePluginId: cgmCurrent.id,
  172. newManager: manager
  173. )
  174. }
  175. func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
  176. // nothing to do ?
  177. }
  178. }