import CGMBLEKit import Combine import G7SensorKit import LoopKitUI import SwiftUI struct cgmName: Identifiable, Hashable { var id: String var type: CGMType var displayName: String var subtitle: String } let cgmDefaultName = cgmName( id: CGMType.none.id, type: .none, displayName: CGMType.none.displayName, subtitle: CGMType.none.subtitle ) extension CGM { final class StateModel: BaseStateModel { @Injected() var cgmManager: FetchGlucoseManager! @Injected() var pluginCGMManager: PluginManager! @Injected() private var broadcaster: Broadcaster! @Injected() var nightscoutManager: NightscoutManager! @Published var units: GlucoseUnits = .mgdL @Published var setupCGM: Bool = false @Published var cgmCurrent = cgmDefaultName @Published var smoothGlucose = false @Published var cgmTransmitterDeviceAddress: String? = nil @Published var listOfCGM: [cgmName] = [] @Published var url: URL? override func subscribe() { units = settingsManager.settings.units // collect the list of CGM available with plugins and CGMType defined manually listOfCGM = ( CGMType.allCases.filter { $0 != CGMType.plugin }.map { cgmName(id: $0.id, type: $0, displayName: $0.displayName, subtitle: $0.subtitle) } + pluginCGMManager.availableCGMManagers.map { cgmName( id: $0.identifier, type: CGMType.plugin, displayName: $0.localizedTitle, subtitle: $0.localizedTitle ) } ).sorted(by: { lhs, rhs in if lhs.displayName == "None" { return true } else if rhs.displayName == "None" { return false } else { return lhs.displayName < rhs.displayName } }) switch settingsManager.settings.cgm { case .plugin: if let cgmPluginInfo = listOfCGM.first(where: { $0.id == settingsManager.settings.cgmPluginIdentifier }) { cgmCurrent = cgmName( id: settingsManager.settings.cgmPluginIdentifier, type: .plugin, displayName: cgmPluginInfo.displayName, subtitle: cgmPluginInfo.subtitle ) } else { // no more type of plugin available - restart to defaut cgmCurrent = cgmDefaultName } default: cgmCurrent = cgmName( id: settingsManager.settings.cgm.id, type: settingsManager.settings.cgm, displayName: settingsManager.settings.cgm.displayName, subtitle: settingsManager.settings.cgm.subtitle ) } url = nightscoutManager.cgmURL switch url?.absoluteString { case "http://127.0.0.1:1979": url = URL(string: "spikeapp://")! case "http://127.0.0.1:17580": url = URL(string: "diabox://")! // case CGMType.libreTransmitter.appURL?.absoluteString: // showModal(for: .libreConfig) default: break } cgmTransmitterDeviceAddress = UserDefaults.standard.cgmTransmitterDeviceAddress subscribeSetting(\.smoothGlucose, on: $smoothGlucose, initial: { smoothGlucose = $0 }) $cgmCurrent .removeDuplicates() .sink { [weak self] value in guard let self = self else { return } guard self.cgmManager.cgmGlucoseSourceType != nil else { self.settingsManager.settings.cgm = .none return } if value.type != self.settingsManager.settings.cgm || value.id != self.settingsManager.settings.cgmPluginIdentifier { self.settingsManager.settings.cgm = value.type self.settingsManager.settings.cgmPluginIdentifier = value.id self.cgmManager.updateGlucoseSource( cgmGlucoseSourceType: value.type, cgmGlucosePluginId: value.id ) self.setupCGM = false } } .store(in: &lifetime) } func displayNameOfApp() -> String? { guard cgmManager != nil else { return nil } var nameOfApp = "Open Application" switch cgmManager.cgmGlucoseSourceType { case .plugin: nameOfApp = "Open " + (cgmManager.cgmManager?.localizedTitle ?? "Application") default: nameOfApp = "Open " + cgmManager.cgmGlucoseSourceType.displayName } return nameOfApp } func urlOfApp() -> URL? { guard cgmManager != nil else { return nil } switch cgmManager.cgmGlucoseSourceType { case .plugin: return cgmManager.cgmManager?.appURL default: return cgmManager.cgmGlucoseSourceType.appURL } } } } extension CGM.StateModel: CompletionDelegate { func completionNotifyingDidComplete(_: CompletionNotifying) { setupCGM = false // if CGM was deleted if cgmManager.cgmGlucoseSourceType == nil { cgmCurrent = cgmDefaultName settingsManager.settings.cgm = cgmDefaultName.type settingsManager.settings.cgmPluginIdentifier = cgmDefaultName.id cgmManager.deleteGlucoseSource() } else { cgmManager.updateGlucoseSource(cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id) } // update if required the Glucose source DispatchQueue.main.async { self.broadcaster.notify(GlucoseObserver.self, on: .main) { $0.glucoseDidUpdate([]) } } } } extension CGM.StateModel: CGMManagerOnboardingDelegate { func cgmManagerOnboarding(didCreateCGMManager manager: LoopKitUI.CGMManagerUI) { // update the glucose source cgmManager.updateGlucoseSource( cgmGlucoseSourceType: cgmCurrent.type, cgmGlucosePluginId: cgmCurrent.id, newManager: manager ) } func cgmManagerOnboarding(didOnboardCGMManager _: LoopKitUI.CGMManagerUI) { // nothing to do ? } } extension CGM.StateModel { func settingsDidChange(_: FreeAPSSettings) { units = settingsManager.settings.units } }