CGMStateModel.swift 6.7 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 pluginCGMManager: PluginManager!
  22. @Injected() private var broadcaster: Broadcaster!
  23. @Injected() var nightscoutManager: NightscoutManager!
  24. @Published var setupCGM: Bool = false
  25. @Published var cgmCurrent = cgmDefaultName
  26. @Published var smoothGlucose = false
  27. @Published var cgmTransmitterDeviceAddress: String? = nil
  28. @Published var listOfCGM: [cgmName] = []
  29. @Published var url: URL?
  30. override func subscribe() {
  31. // collect the list of CGM available with plugins and CGMType defined manually
  32. listOfCGM = (
  33. CGMType.allCases.filter { $0 != CGMType.plugin }.map {
  34. cgmName(id: $0.id, type: $0, displayName: $0.displayName, subtitle: $0.subtitle)
  35. } +
  36. pluginCGMManager.availableCGMManagers.map {
  37. cgmName(
  38. id: $0.identifier,
  39. type: CGMType.plugin,
  40. displayName: $0.localizedTitle,
  41. subtitle: $0.localizedTitle
  42. )
  43. }
  44. ).sorted(by: { lhs, rhs in
  45. if lhs.displayName == "None" {
  46. return true
  47. } else if rhs.displayName == "None" {
  48. return false
  49. } else {
  50. return lhs.displayName < rhs.displayName
  51. }
  52. })
  53. switch settingsManager.settings.cgm {
  54. case .plugin:
  55. if let cgmPluginInfo = listOfCGM.first(where: { $0.id == settingsManager.settings.cgmPluginIdentifier }) {
  56. cgmCurrent = cgmName(
  57. id: settingsManager.settings.cgmPluginIdentifier,
  58. type: .plugin,
  59. displayName: cgmPluginInfo.displayName,
  60. subtitle: cgmPluginInfo.subtitle
  61. )
  62. } else {
  63. // no more type of plugin available - restart to defaut
  64. cgmCurrent = cgmDefaultName
  65. }
  66. default:
  67. cgmCurrent = cgmName(
  68. id: settingsManager.settings.cgm.id,
  69. type: settingsManager.settings.cgm,
  70. displayName: settingsManager.settings.cgm.displayName,
  71. subtitle: settingsManager.settings.cgm.subtitle
  72. )
  73. }
  74. url = nightscoutManager.cgmURL
  75. switch url?.absoluteString {
  76. case "http://127.0.0.1:1979":
  77. url = URL(string: "spikeapp://")!
  78. case "http://127.0.0.1:17580":
  79. url = URL(string: "diabox://")!
  80. // case CGMType.libreTransmitter.appURL?.absoluteString:
  81. // showModal(for: .libreConfig)
  82. default: break
  83. }
  84. cgmTransmitterDeviceAddress = UserDefaults.standard.cgmTransmitterDeviceAddress
  85. subscribeSetting(\.smoothGlucose, on: $smoothGlucose, initial: { smoothGlucose = $0 })
  86. $cgmCurrent
  87. .removeDuplicates()
  88. .sink { [weak self] value in
  89. guard let self = self else { return }
  90. guard self.cgmManager.cgmGlucoseSourceType != nil else {
  91. self.settingsManager.settings.cgm = .none
  92. return
  93. }
  94. if value.type != self.settingsManager.settings.cgm ||
  95. value.id != self.settingsManager.settings.cgmPluginIdentifier
  96. {
  97. self.settingsManager.settings.cgm = value.type
  98. self.settingsManager.settings.cgmPluginIdentifier = value.id
  99. self.cgmManager.updateGlucoseSource(
  100. cgmGlucoseSourceType: value.type,
  101. cgmGlucosePluginId: value.id
  102. )
  103. self.setupCGM = false
  104. }
  105. }
  106. .store(in: &lifetime)
  107. }
  108. func displayNameOfApp() -> String? {
  109. guard cgmManager != nil else { return nil }
  110. var nameOfApp = "Open Application"
  111. switch cgmManager.cgmGlucoseSourceType {
  112. case .plugin:
  113. nameOfApp = "Open " + (cgmManager.cgmManager?.localizedTitle ?? "Application")
  114. default:
  115. nameOfApp = "Open " + cgmManager.cgmGlucoseSourceType.displayName
  116. }
  117. return nameOfApp
  118. }
  119. func urlOfApp() -> URL? {
  120. guard cgmManager != nil else { return nil }
  121. switch cgmManager.cgmGlucoseSourceType {
  122. case .plugin:
  123. return cgmManager.cgmManager?.appURL
  124. default:
  125. return cgmManager.cgmGlucoseSourceType.appURL
  126. }
  127. }
  128. }
  129. }
  130. extension CGM.StateModel: CompletionDelegate {
  131. func completionNotifyingDidComplete(_: CompletionNotifying) {
  132. setupCGM = false
  133. // if CGM was deleted
  134. if cgmManager.cgmGlucoseSourceType == nil {
  135. cgmCurrent = cgmDefaultName
  136. settingsManager.settings.cgm = cgmDefaultName.type
  137. settingsManager.settings.cgmPluginIdentifier = cgmDefaultName.id
  138. cgmManager.deleteGlucoseSource()
  139. } else {
  140. cgmManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id)
  141. }
  142. // update if required the Glucose source
  143. DispatchQueue.main.async {
  144. self.broadcaster.notify(GlucoseObserver.self, on: .main) {
  145. $0.glucoseDidUpdate([])
  146. }
  147. }
  148. }
  149. }
  150. extension CGM.StateModel: CGMManagerOnboardingDelegate {
  151. func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) {
  152. // update the glucose source
  153. cgmManager.updateGlucoseSource(
  154. cgmGlucoseSourceType: cgmCurrent.type,
  155. cgmGlucosePluginId: cgmCurrent.id,
  156. newManager: manager
  157. )
  158. }
  159. func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) {
  160. // nothing to do ?
  161. }
  162. }