CGMStateModel.swift 6.9 KB

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