// // MinimedPumpSettingsViewModel.swift // MinimedKitUI // // Created by Pete Schwamb on 11/29/22. // Copyright © 2022 LoopKit Authors. All rights reserved. // import Foundation import MinimedKit import LoopKit import SwiftUI import LoopKitUI import HealthKit enum MinimedSettingsViewAlert: Identifiable { case suspendError(Error) case resumeError(Error) case syncTimeError(PumpManagerError) var id: String { switch self { case .suspendError: return "suspendError" case .resumeError: return "resumeError" case .syncTimeError: return "syncTimeError" } } } enum MySentryConfig: Int, Identifiable, CaseIterable { var id: RawValue { return self.rawValue } case useMySentry = 0 case doNotUseMySentry var localizedDescription: String { switch self { case .useMySentry: return LocalizedString("Use MySentry", comment: "Description for option to use MySentry") case .doNotUseMySentry: return LocalizedString("Do not use MySentry", comment: "Description for option to not use MySentry") } } } public enum ReservoirLevelHighlightState: String, Equatable { case normal case warning case critical } class MinimedPumpSettingsViewModel: ObservableObject { @Published var suspendResumeTransitioning: Bool = false @Published var basalDeliveryState: PumpManagerStatus.BasalDeliveryState? @Published var reservoirReading: ReservoirReading? @Published var batteryChemistryType: BatteryChemistryType { didSet { pumpManager.batteryChemistry = batteryChemistryType } } @Published var preferredDataSource: InsulinDataSource { didSet { pumpManager.preferredInsulinDataSource = preferredDataSource } } @Published var mySentryConfig: MySentryConfig { didSet { pumpManager.useMySentry = mySentryConfig == .useMySentry } } @Published var activeAlert: MinimedSettingsViewAlert? @Published var suspendResumeButtonEnabled: Bool = false @Published var synchronizingTime: Bool = false var pumpManager: MinimedPumpManager var didFinish: (() -> Void)? let basalRateFormatter: NumberFormatter = { let numberFormatter = NumberFormatter() numberFormatter.numberStyle = .decimal numberFormatter.minimumFractionDigits = 1 numberFormatter.minimumIntegerDigits = 1 return numberFormatter }() let reservoirVolumeFormatter = { let formatter = QuantityFormatter(for: .internationalUnit()) formatter.numberFormatter.maximumFractionDigits = 1 return formatter }() init(pumpManager: MinimedPumpManager) { self.pumpManager = pumpManager self.basalDeliveryState = pumpManager.status.basalDeliveryState self.reservoirReading = pumpManager.state.lastReservoirReading self.batteryChemistryType = pumpManager.batteryChemistry self.preferredDataSource = pumpManager.preferredInsulinDataSource self.mySentryConfig = pumpManager.useMySentry ? .useMySentry : .doNotUseMySentry self.pumpManager.addStatusObserver(self, queue: DispatchQueue.main) pumpManager.stateObservers.insert(self, queue: .main) } var pumpImage: UIImage { return pumpManager.state.largePumpImage } func deletePump() { pumpManager.deletePump { DispatchQueue.main.async { self.didFinish?() } } } func doneButtonPressed() { self.didFinish?() } func suspendResumeButtonPressed(action: SuspendResumeAction) { suspendResumeButtonEnabled = true switch action { case .resume: pumpManager.resumeDelivery { error in DispatchQueue.main.async { self.suspendResumeButtonEnabled = false if let error = error { self.activeAlert = .resumeError(error) } } } case .suspend: pumpManager.suspendDelivery { error in DispatchQueue.main.async { self.suspendResumeButtonEnabled = false if let error = error { self.activeAlert = .suspendError(error) } } } } } func didChangeInsulinType(_ newType: InsulinType?) { self.pumpManager.insulinType = newType } var isScheduledBasal: Bool { switch basalDeliveryState { case .active(_), .initiatingTempBasal: return true case .tempBasal(_), .cancelingTempBasal, .suspending, .suspended(_), .resuming, .none: return false } } var isSuspendedOrResuming: Bool { switch basalDeliveryState { case .suspended, .resuming: return true default: return false } } func suspendResumeButtonColor(guidanceColors: GuidanceColors) -> Color { switch basalDeliveryState { case .suspending, .resuming: return Color.secondary case .suspended: return guidanceColors.warning default: return .accentColor } } var basalDeliveryRate: Double? { switch basalDeliveryState { case .suspending, .resuming, .suspended, .none, .initiatingTempBasal, .cancelingTempBasal: return nil case .active: // return scheduled basal rate var calendar = Calendar(identifier: .gregorian) calendar.timeZone = pumpManager.state.timeZone return pumpManager.state.basalSchedule.currentRate(using: calendar) case .tempBasal(let dose): return dose.unitsPerHour } } public var reservoirLevelHighlightState: ReservoirLevelHighlightState? { guard let reservoirReading = reservoirReading else { return nil } let value = reservoirReading.units if value > pumpManager.lowReservoirWarningLevel { return .normal } else if value > 0 { return .warning } else { return .critical } } public var reservoirPercentage: Double? { guard let reservoirReading = reservoirReading else { return nil } return (reservoirReading.units / pumpManager.pumpReservoirCapacity).clamped(to: 0...1.0) } func reservoirText(for units: Double) -> String { let quantity = HKQuantity(unit: .internationalUnit(), doubleValue: units) return reservoirVolumeFormatter.string(from: quantity, for: .internationalUnit()) ?? "" } var isClockOffset: Bool { return pumpManager.isClockOffset } func changeTimeZoneTapped() { synchronizingTime = true pumpManager.setTime { (error) in DispatchQueue.main.async { self.synchronizingTime = false if let error = error { self.activeAlert = .syncTimeError(error) } } } } } extension MinimedPumpSettingsViewModel: PumpManagerStatusObserver { public func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) { basalDeliveryState = status.basalDeliveryState } } extension MinimedPumpSettingsViewModel: MinimedPumpManagerStateObserver { func didUpdatePumpManagerState(_ state: MinimedKit.MinimedPumpManagerState) { reservoirReading = state.lastReservoirReading batteryChemistryType = state.batteryChemistry preferredDataSource = state.preferredInsulinDataSource mySentryConfig = state.useMySentry ? .useMySentry : .doNotUseMySentry } } enum SuspendResumeAction { case suspend case resume } extension PumpManagerStatus.BasalDeliveryState { var shownAction: SuspendResumeAction { switch self { case .active, .suspending, .tempBasal, .cancelingTempBasal, .initiatingTempBasal: return .suspend case .suspended, .resuming: return .resume } } var buttonLabelText: String { switch self { case .active, .tempBasal: return LocalizedString("Suspend Delivery", comment: "Title text for button to suspend insulin delivery") case .suspending: return LocalizedString("Suspending", comment: "Title text for button when insulin delivery is in the process of being stopped") case .suspended: return LocalizedString("Resume Delivery", comment: "Title text for button to resume insulin delivery") case .resuming: return LocalizedString("Resuming", comment: "Title text for button when insulin delivery is in the process of being resumed") case .initiatingTempBasal: return LocalizedString("Starting Temp Basal", comment: "Title text for suspend resume button when temp basal starting") case .cancelingTempBasal: return LocalizedString("Canceling Temp Basal", comment: "Title text for suspend resume button when temp basal canceling") } } var isTransitioning: Bool { switch self { case .suspending, .resuming, .initiatingTempBasal, .cancelingTempBasal: return true default: return false } } }