diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index 7e5d880..e47ae49 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -148,13 +148,11 @@ C12EDA0429C7DDC800435701 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0329C7DDC800435701 /* TimeInterval.swift */; }; C12EDA0629C7DE2500435701 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0529C7DE2500435701 /* TimeZone.swift */; }; C12EDA0829C7DE6200435701 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0729C7DE6200435701 /* NumberFormatter.swift */; }; - C12EDA0A29C7DEAA00435701 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0929C7DEAA00435701 /* HKUnit.swift */; }; C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0B29C7DED000435701 /* LocalizedString.swift */; }; C12EDA0E29C7DEFD00435701 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0D29C7DEFD00435701 /* NumberFormatter.swift */; }; C12EDA1029C7DF1900435701 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0F29C7DF1900435701 /* NibLoadable.swift */; }; C12EDA1229C7DF4B00435701 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */; }; C12EDA1429C7DFBF00435701 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1329C7DFBF00435701 /* TimeInterval.swift */; }; - C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1529C7DFF100435701 /* HKUnit.swift */; }; C12EDA1829C7E01800435701 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1729C7E01800435701 /* TimeZone.swift */; }; C12EDA1B29C7E06900435701 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA1A29C7E06900435701 /* OSLog.swift */; }; D80339732A500489004FF953 /* PodInfoPulseLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C124017E29C7D8E900B32844 /* PodInfoPulseLog.swift */; }; @@ -401,13 +399,11 @@ C12EDA0329C7DDC800435701 /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; C12EDA0529C7DE2500435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA0729C7DE6200435701 /* NumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = ""; }; - C12EDA0929C7DEAA00435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA0B29C7DED000435701 /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; C12EDA0D29C7DEFD00435701 /* NumberFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = ""; }; C12EDA0F29C7DF1900435701 /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; C12EDA1329C7DFBF00435701 /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; - C12EDA1529C7DFF100435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA1729C7E01800435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA1A29C7E06900435701 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; D845A1342AF89DEC00EA0853 /* SilencePodPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SilencePodPreference.swift; sourceTree = ""; }; @@ -529,7 +525,6 @@ C12EDA0329C7DDC800435701 /* TimeInterval.swift */, C12EDA0529C7DE2500435701 /* TimeZone.swift */, C12EDA0729C7DE6200435701 /* NumberFormatter.swift */, - C12EDA0929C7DEAA00435701 /* HKUnit.swift */, ); path = Extensions; sourceTree = ""; @@ -671,7 +666,6 @@ C12EDA0F29C7DF1900435701 /* NibLoadable.swift */, C12EDA1129C7DF4B00435701 /* IdentifiableClass.swift */, C12EDA1329C7DFBF00435701 /* TimeInterval.swift */, - C12EDA1529C7DFF100435701 /* HKUnit.swift */, C12EDA1729C7E01800435701 /* TimeZone.swift */, ); path = Extensions; @@ -1155,7 +1149,6 @@ C12401EB29C7D8E900B32844 /* Packet.swift in Sources */, C12401C029C7D8E900B32844 /* PlaceholderMessageBlock.swift in Sources */, C12401BA29C7D8E900B32844 /* PodInfoActivationTime.swift in Sources */, - C12EDA0A29C7DEAA00435701 /* HKUnit.swift in Sources */, C12401C229C7D8E900B32844 /* BolusExtraCommand.swift in Sources */, C12401E929C7D8E900B32844 /* Packet+RFPacket.swift in Sources */, C12401E729C7D8E900B32844 /* OmnipodPumpManager.swift in Sources */, @@ -1227,7 +1220,6 @@ C124028629C7DA9700B32844 /* NotificationSettingsView.swift in Sources */, C124027D29C7DA9700B32844 /* InsertCannulaView.swift in Sources */, C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */, - C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */, C12EDA1829C7E01800435701 /* TimeZone.swift in Sources */, C124026D29C7DA9700B32844 /* Image.swift in Sources */, C124028E29C7DA9700B32844 /* ScheduledExpirationReminderEditView.swift in Sources */, @@ -1375,7 +1367,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, @@ -1441,7 +1433,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, diff --git a/OmniKit/Extensions/HKUnit.swift b/OmniKit/Extensions/HKUnit.swift deleted file mode 100644 index 8311b6c..0000000 --- a/OmniKit/Extensions/HKUnit.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HKUnit.swift -// OmniKit -// -// Created by Pete Schwamb on 3/19/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() - -} diff --git a/OmniKit/OmnipodCommon/PumpManagerAlert.swift b/OmniKit/OmnipodCommon/PumpManagerAlert.swift index b1e4dac..54184d1 100644 --- a/OmniKit/OmnipodCommon/PumpManagerAlert.swift +++ b/OmniKit/OmnipodCommon/PumpManagerAlert.swift @@ -7,8 +7,8 @@ // import Foundation +import LoopAlgorithm import LoopKit -import HealthKit public enum PumpManagerAlert: Hashable { case podExpireImminent(triggeringSlot: AlertSlot?) @@ -70,8 +70,8 @@ public enum PumpManagerAlert: Hashable { case .podExpireImminent: return LocalizedString("Change Pod now. Insulin delivery will stop in 1 hour.", comment: "Alert content body for podExpireImminent pod alert") case .lowReservoir(_, let lowReservoirReminderValue): - let quantityFormatter = QuantityFormatter(for: .internationalUnit()) - let valueString = quantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: lowReservoirReminderValue)) ?? String(describing: lowReservoirReminderValue) + let quantityFormatter = QuantityFormatter(for: .internationalUnit) + let valueString = quantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: lowReservoirReminderValue)) ?? String(describing: lowReservoirReminderValue) return String(format: LocalizedString("%1$@ insulin or less remaining in Pod. Change Pod soon.", comment: "Format string for alert content body for lowReservoir pod alert. (1: reminder value)"), valueString) case .suspendInProgress: return LocalizedString("Suspend In Progress Reminder", comment: "Alert content body for suspendInProgress pod alert") diff --git a/OmniKit/OmnipodCommon/UnfinalizedDose.swift b/OmniKit/OmnipodCommon/UnfinalizedDose.swift index a4f272e..2371795 100644 --- a/OmniKit/OmnipodCommon/UnfinalizedDose.swift +++ b/OmniKit/OmnipodCommon/UnfinalizedDose.swift @@ -65,6 +65,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti var isHighTemp: Bool = false // Track this for situations where cancelling temp basal is unacknowledged, and recovery fails, and we have to assume the most possible delivery var insulinType: InsulinType? + var decisionId: UUID? + var finishTime: Date? { get { return duration != nil ? startTime.addingTimeInterval(duration!) : nil @@ -101,7 +103,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti return units } - init(bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, automatic: Bool = false) { + init(decisionId: UUID?, bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType, automatic: Bool = false) { + self.decisionId = decisionId self.doseType = .bolus self.units = bolusAmount self.startTime = startTime @@ -112,7 +115,8 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.insulinType = insulinType } - init(tempBasalRate: Double, startTime: Date, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType) { + init(decisionId: UUID?, tempBasalRate: Double, startTime: Date, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, scheduledCertainty: ScheduledCertainty, insulinType: InsulinType) { + self.decisionId = decisionId self.doseType = .tempBasal self.units = tempBasalRate * duration.hours self.startTime = startTime @@ -260,6 +264,10 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti if let rawInsulinType = rawValue["insulinType"] as? InsulinType.RawValue { self.insulinType = InsulinType(rawValue: rawInsulinType) } + + if let decisionIdString = rawValue["decisionId"] as? String { + self.decisionId = UUID(uuidString: decisionIdString)! + } } @@ -277,6 +285,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti rawValue["scheduledTempRate"] = scheduledTempRate rawValue["duration"] = duration rawValue["insulinType"] = insulinType?.rawValue + rawValue["decisionId"] = decisionId?.uuidString return rawValue } @@ -311,6 +320,7 @@ extension DoseEntry { endDate: dose.finishTime, value: dose.scheduledUnits ?? dose.units, unit: .units, + decisionId: dose.decisionId, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, @@ -323,6 +333,7 @@ extension DoseEntry { endDate: dose.finishTime, value: dose.scheduledTempRate ?? dose.rate, unit: .unitsPerHour, + decisionId: dose.decisionId, deliveredUnits: dose.finalizedUnits, insulinType: dose.insulinType, automatic: dose.automatic, @@ -340,9 +351,9 @@ extension StartProgram { func unfinalizedDose(at programDate: Date, withCertainty certainty: UnfinalizedDose.ScheduledCertainty, insulinType: InsulinType) -> UnfinalizedDose? { switch self { case .bolus(volume: let volume, automatic: let automatic): - return UnfinalizedDose(bolusAmount: volume, startTime: programDate, scheduledCertainty: certainty, insulinType: insulinType, automatic: automatic) + return UnfinalizedDose(decisionId: nil, bolusAmount: volume, startTime: programDate, scheduledCertainty: certainty, insulinType: insulinType, automatic: automatic) case .tempBasal(unitsPerHour: let rate, duration: let duration, let isHighTemp, let automatic): - return UnfinalizedDose(tempBasalRate: rate, startTime: programDate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: certainty, insulinType: insulinType) + return UnfinalizedDose(decisionId: nil, tempBasalRate: rate, startTime: programDate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: certainty, insulinType: insulinType) case .basalProgram: return UnfinalizedDose(resumeStartTime: programDate, scheduledCertainty: certainty, insulinType: insulinType) } diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 16a0da7..7cf37d7 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -92,7 +92,7 @@ extension OmnipodPumpManagerError: LocalizedError { public class OmnipodPumpManager: RileyLinkPumpManager { - public static let pluginIdentifier: String = "Omnipod" + public let pluginIdentifier: String = "Omnipod" public let localizedTitle = LocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager") @@ -274,11 +274,11 @@ public class OmnipodPumpManager: RileyLinkPumpManager { self.setState { state in state.lastRileyLinkBatteryAlertDate = Date() } - self.pumpDelegate.notify { delegate in + Task { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: "lowRLBattery") let alertBody = String(format: LocalizedString("\"%1$@\" has a low battery", comment: "Format string for low battery alert body for RileyLink. (1: device name)"), device.name ?? "unnamed") let content = Alert.Content(title: LocalizedString("Low RileyLink Battery", comment: "Title for RileyLink low battery alert"), body: alertBody, acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Acknowledge button label for RileyLink low battery alert")) - delegate?.issueAlert(Alert(identifier: identifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)) + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: identifier, foregroundContent: content, backgroundContent: content, trigger: .immediate)) } } } @@ -374,7 +374,7 @@ extension OmnipodPumpManager { localizedMessage: LocalizedString("Insulin Suspended", comment: "Status highlight that insulin delivery was suspended."), imageName: "pause.circle.fill", state: .warning) - } else if date.timeIntervalSince(state.lastPumpDataReportDate ?? .distantPast) > .minutes(12) { + } else if isSignalLost(at: date, lastPumpDataReportDate: state.lastPumpDataReportDate) { return PumpStatusHighlight( localizedMessage: LocalizedString("Signal Loss", comment: "Status highlight when communications with the pod haven't happened recently."), imageName: "exclamationmark.circle.fill", @@ -388,6 +388,10 @@ extension OmnipodPumpManager { return nil } } + + private func isSignalLost(at date: Date, lastPumpDataReportDate: Date?) -> Bool { + date.timeIntervalSince(lastPumpDataReportDate ?? .distantPast) > .minutes(12) + } public func isRunningManualTempBasal(for state: OmnipodPumpManagerState) -> Bool { if let tempBasal = state.podState?.unfinalizedTempBasal, !tempBasal.automatic { @@ -489,6 +493,13 @@ extension OmnipodPumpManager { return .active(.distantPast) } + switch podCommState(for: state) { + case .fault: + return .pumpInoperable + default: + break + } + switch state.suspendEngageState { case .engaging: return .suspending @@ -1549,6 +1560,14 @@ extension OmnipodPumpManager { // MARK: - PumpManager extension OmnipodPumpManager: PumpManager { + public var inSignalLoss: Bool { + isSignalLost(at: Date(), lastPumpDataReportDate: state.lastPumpDataReportDate) + } + + public var isInoperable: Bool { + basalDeliveryState(for: state) == .pumpInoperable + } + public static var onboardingMaximumBasalScheduleEntryCount: Int { return Pod.maximumBasalScheduleEntryCount } @@ -1825,9 +1844,9 @@ extension OmnipodPumpManager: PumpManager { } fileprivate func clearSuspendReminder() { - self.pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).alertIdentifier)) - delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).repeatingAlertIdentifier)) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).alertIdentifier)) + await self.pumpDelegate.delegate?.retractAlert(identifier: Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: PumpManagerAlert.suspendEnded(triggeringSlot: nil).repeatingAlertIdentifier)) } } @@ -1879,7 +1898,7 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactBolus(units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) { + public func enactBolus(decisionId: UUID?, units: Double, activationType: BolusActivationType, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmnipodPumpManagerError.noPodPaired)) return @@ -1933,7 +1952,7 @@ extension OmnipodPumpManager: PumpManager { // Use a maximum programReminderInterval value of 0x3F to denote an automatic bolus in the communication log let programReminderInterval: TimeInterval = activationType.isAutomatic ? TimeInterval(minutes: 0x3F) : 0 - let result = session.bolus(units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + let result = session.bolus(decisionId: decisionId, units: enactUnits, automatic: activationType.isAutomatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) switch result { case .success: @@ -2016,11 +2035,11 @@ extension OmnipodPumpManager: PumpManager { } } - public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { - runTemporaryBasalProgram(unitsPerHour: unitsPerHour, for: duration, automatic: true, completion: completion) + public func enactTempBasal(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { + runTemporaryBasalProgram(decisionId: decisionId, unitsPerHour: unitsPerHour, for: duration, automatic: true, completion: completion) } - public func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) { + public func runTemporaryBasalProgram(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) { guard self.hasActivePod else { completion(.configuration(OmnipodPumpManagerError.noPodPaired)) @@ -2147,7 +2166,7 @@ extension OmnipodPumpManager: PumpManager { let scheduledRate = self.state.basalSchedule.currentRate(using: calendar, at: self.dateGenerator()) let isHighTemp = rate > scheduledRate - let result = session.setTempBasal(rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) + let result = session.setTempBasal(decisionId: decisionId, rate: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) switch result { case .success: session.dosesForStorage() { (doses) -> Bool in @@ -2340,16 +2359,16 @@ extension OmnipodPumpManager: PumpManager { func issueAlert(alert: PumpManagerAlert) { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.alertIdentifier) let loopAlert = Alert(identifier: identifier, foregroundContent: alert.foregroundContent, backgroundContent: alert.backgroundContent, trigger: .immediate) - pumpDelegate.notify { (delegate) in - delegate?.issueAlert(loopAlert) + Task { + await self.pumpDelegate.delegate?.issueAlert(loopAlert) } if let repeatInterval = alert.repeatInterval { // Schedule an additional repeating 15 minute reminder for suspend period ended. let repeatingIdentifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.repeatingAlertIdentifier) let loopAlert = Alert(identifier: repeatingIdentifier, foregroundContent: alert.foregroundContent, backgroundContent: alert.backgroundContent, trigger: .repeating(repeatInterval: repeatInterval)) - pumpDelegate.notify { (delegate) in - delegate?.issueAlert(loopAlert) + Task { + await self.pumpDelegate.delegate?.issueAlert(loopAlert) } } @@ -2360,13 +2379,13 @@ extension OmnipodPumpManager: PumpManager { func retractAlert(alert: PumpManagerAlert) { let identifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.alertIdentifier) - pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: identifier) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: identifier) } if alert.isRepeating { let repeatingIdentifier = Alert.Identifier(managerIdentifier: self.pluginIdentifier, alertIdentifier: alert.repeatingAlertIdentifier) - pumpDelegate.notify { (delegate) in - delegate?.retractAlert(identifier: repeatingIdentifier) + Task { + await self.pumpDelegate.delegate?.retractAlert(identifier: repeatingIdentifier) } } self.setState { (state) in @@ -2450,11 +2469,24 @@ extension OmnipodPumpManager: PumpManager { static let podAlarmNotificationIdentifier = "Omnipod:\(LoopNotificationCategory.pumpFault.rawValue)" private func notifyPodFault(fault: DetailedStatus) { + // Record the fault as a pump event so it is persisted to the pump event store and + // uploaded to remote services (e.g. Nightscout, as a note). This fires once per + // fault, on the no-fault→fault transition in podComms(_:didChange:). pumpDelegate.notify { delegate in + let date = Date() + let event = NewPumpEvent(date: date, + dose: nil, + raw: "Pod Fault \(fault.faultEventCode.rawValue) \(date)".data(using: .utf8)!, + title: fault.faultEventCode.description, + type: .alarm) + delegate?.pumpManager(self, hasNewPumpEvents: [event], lastReconciliation: self.lastSync, replacePendingEvents: false) { _ in } + } + + Task { let content = Alert.Content(title: fault.faultEventCode.notificationTitle, body: fault.faultEventCode.notificationBody, acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Alert acknowledgment OK button")) - delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmnipodPumpManager.podAlarmNotificationIdentifier, + await self.pumpDelegate.delegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: OmnipodPumpManager.podAlarmNotificationIdentifier, alertIdentifier: fault.faultEventCode.description), foregroundContent: content, backgroundContent: content, trigger: .immediate)) @@ -2566,11 +2598,9 @@ extension OmnipodPumpManager: PodCommsDelegate { // MARK: - AlertResponder implementation extension OmnipodPumpManager { - public func acknowledgeAlert(alertIdentifier: Alert.AlertIdentifier, completion: @escaping (Error?) -> Void) { - guard self.hasActivePod, !state.activeAlerts.isEmpty else { - log.default("Skipping acknowledge alert %{public}@ with no active pod or alerts", alertIdentifier) - completion(nil) - return + public func acknowledgeAlert(alertIdentifier: LoopKit.Alert.AlertIdentifier) async throws { + guard self.hasActivePod else { + throw OmnipodPumpManagerError.noPodPaired } var found = false @@ -2586,34 +2616,34 @@ extension OmnipodPumpManager { // Don't clear this pod alert here with the pod still suspended so that the suspend time expired // pod alert beeping will continue until the pod is resumed which will then deactivate this alert. log.default("Skipping acknowledgement of suspend time expired alert with a suspended pod") - completion(nil) return } // Acknowledge the pod alert for the triggering slot let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice - self.podComms.runSession(withName: "Acknowledge Alert", using: rileyLinkSelector) { (result) in - switch result { - case .success(let session): - do { - let beepBlock = self.beepMessageBlock(beepType: .beep) - let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) - } catch { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in + self.podComms.runSession(withName: "Acknowledge Alert", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + let beepBlock = self.beepMessageBlock(beepType: .beep) + let _ = try session.acknowledgeAlerts(alerts: AlertSet(slots: [slot]), beepBlock: beepBlock) + } catch { + self.setState { state in + state.alertsWithPendingAcknowledgment.insert(alert) + } + continuation.resume(throwing: error) + } + self.setState { state in + state.activeAlerts.remove(alert) + } + continuation.resume() + case .failure(let error): self.setState { state in state.alertsWithPendingAcknowledgment.insert(alert) } - completion(error) - return - } - self.setState { state in - state.activeAlerts.remove(alert) + continuation.resume(throwing: error) } - completion(nil) - case .failure(let error): - self.setState { state in - state.alertsWithPendingAcknowledgment.insert(alert) - } - completion(error) } } } else { @@ -2624,14 +2654,13 @@ extension OmnipodPumpManager { state.acknowledgedTimeOffsetAlert = true } } - completion(nil) } } } if !found { log.error("acknowledge alert %{public}@ not found!", alertIdentifier) - completion(nil) + // alert not found — no-op in async version } } } diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift index 507454a..9490e51 100644 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -18,7 +18,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { public var isOnboarded: Bool - private (set) public var podState: PodState? + public private(set) var podState: PodState? // podState should only be modifiable by PodComms mutating func updatePodStateFromPodComms(_ podState: PodState?) { diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 1d6195f..725b10d 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -557,7 +557,7 @@ public class PodCommsSession { } - public func bolus(units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { + public func bolus(decisionId: UUID?, units: Double, automatic: Bool = false, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, extendedUnits: Double = 0.0, extendedDuration: TimeInterval = 0) -> DeliveryCommandResult { if podState.unacknowledgedCommand != nil { do { @@ -595,7 +595,7 @@ public class PodCommsSession { podState.unacknowledgedCommand = PendingCommand.program(.bolus(volume: units, automatic: automatic), transport.messageNumber, currentDate) let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) podState.unacknowledgedCommand = nil - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType, automatic: automatic) + podState.unfinalizedBolus = UnfinalizedDose(decisionId: decisionId, bolusAmount: units, startTime: currentDate, scheduledCertainty: .certain, insulinType: podState.insulinType, automatic: automatic) podState.updateFromStatusResponse(statusResponse, at: currentDate) return DeliveryCommandResult.success(statusResponse: statusResponse) } catch PodCommsError.unacknowledgedMessage(let seq, let error) { @@ -608,7 +608,7 @@ public class PodCommsSession { } } - public func setTempBasal(rate: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { + public func setTempBasal(decisionId: UUID?, rate: Double, duration: TimeInterval, isHighTemp: Bool, automatic: Bool, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { if podState.unacknowledgedCommand != nil { do { @@ -631,7 +631,7 @@ public class PodCommsSession { podState.unacknowledgedCommand = PendingCommand.program(.tempBasal(unitsPerHour: rate, duration: duration, isHighTemp: isHighTemp, automatic: automatic), transport.messageNumber, startTime) let status: StatusResponse = try send([tempBasalCommand, tempBasalExtraCommand]) podState.unacknowledgedCommand = nil - podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: startTime, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: .certain, insulinType: podState.insulinType) + podState.unfinalizedTempBasal = UnfinalizedDose(decisionId: decisionId, tempBasalRate: rate, startTime: startTime, duration: duration, isHighTemp: isHighTemp, automatic: automatic, scheduledCertainty: .certain, insulinType: podState.insulinType) podState.updateFromStatusResponse(status, at: currentDate) return DeliveryCommandResult.success(statusResponse: status) } catch PodCommsError.unacknowledgedMessage(let seq, let error) { diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index deedda7..3d73836 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -296,7 +296,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl if deliveryStatus.bolusing && unfinalizedBolus == nil { // active bolus that we aren't tracking if podProgressStatus.readyForDelivery { // Create an unfinalizedBolus with the remaining bolus amount to capture what we can. - unfinalizedBolus = UnfinalizedDose(bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) + unfinalizedBolus = UnfinalizedDose(decisionId: nil, bolusAmount: bolusNotDelivered, startTime: date, scheduledCertainty: .certain, insulinType: insulinType, automatic: false) } } if deliveryStatus.tempBasalRunning && unfinalizedTempBasal == nil { // active temp basal that we aren't tracking diff --git a/OmniKitTests/PodCommsSessionTests.swift b/OmniKitTests/PodCommsSessionTests.swift index 9d1302c..1d1a54f 100644 --- a/OmniKitTests/PodCommsSessionTests.swift +++ b/OmniKitTests/PodCommsSessionTests.swift @@ -67,9 +67,8 @@ class PodCommsSessionTests: XCTestCase { // Try sending another bolus command: nonce should be 545302454 XCTAssertEqual(545302454, lastPodStateUpdate!.currentNonce) - let _ = session.bolus(units: 2, automatic: false) - let lastSentMessageIndex = mockTransport.sentMessages.endIndex - 1 - let bolusTry3 = mockTransport.sentMessages[lastSentMessageIndex].messageBlocks[0] as! SetInsulinScheduleCommand + let _ = session.bolus(decisionId: nil, units: 2, automatic: false) + let bolusTry3 = mockTransport.sentMessages[2].messageBlocks[0] as! SetInsulinScheduleCommand XCTAssertEqual(545302454, bolusTry3.nonce) } @@ -79,7 +78,7 @@ class PodCommsSessionTests: XCTestCase { mockTransport.throwSendMessageError = PodCommsError.unacknowledgedMessage(sequenceNumber: 5, error: PodCommsError.noResponse) - let _ = session.bolus(units: 3) + let _ = session.bolus(decisionId: nil, units: 3) XCTAssertNotNil(lastPodStateUpdate?.unacknowledgedCommand) @@ -87,7 +86,7 @@ class PodCommsSessionTests: XCTestCase { func testBolusFinishedEarlyOnPodIsMarkedNonMutable() { let mockStart = Date() - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: 4.45, startTime: mockStart, scheduledCertainty: .certain, insulinType: .novolog) + podState.unfinalizedBolus = UnfinalizedDose(decisionId: nil, bolusAmount: 4.45, startTime: mockStart, scheduledCertainty: .certain, insulinType: .novolog) let session = PodCommsSession(podState: podState, transport: mockTransport, delegate: self) // Simulate a status request a bit before the bolus is expected to finish @@ -131,7 +130,7 @@ class PodCommsSessionTests: XCTestCase { mockTransport.addResponse(statusResponse) - let _ = session.bolus(units: 3) + let _ = session.bolus(decisionId: nil, units: 3) XCTAssertNil(lastPodStateUpdate?.unacknowledgedCommand) XCTAssertNotNil(lastPodStateUpdate?.unfinalizedBolus) diff --git a/OmniKitUI/Extensions/HKUnit.swift b/OmniKitUI/Extensions/HKUnit.swift deleted file mode 100644 index 7bf65cd..0000000 --- a/OmniKitUI/Extensions/HKUnit.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// HKUnit.swift -// OmniKitUI -// -// Created by Pete Schwamb on 3/19/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() - -} diff --git a/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift index 681e5a0..883e62f 100644 --- a/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift +++ b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift @@ -67,7 +67,7 @@ public enum OmniKitStatusBadge: DeviceStatusBadge { // MARK: - PumpStatusIndicator extension OmnipodPumpManager { - public var pumpStatusHighlight: DeviceStatusHighlight? { + public var pumpStatusHighlight: PumpStatusHighlight? { buildPumpStatusHighlight(for: state) } diff --git a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift index 3dae32c..39098f0 100644 --- a/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift +++ b/OmniKitUI/ViewModels/OmnipodSettingsViewModel.swift @@ -9,7 +9,7 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit +import LoopAlgorithm import OmniKit import Combine @@ -161,7 +161,7 @@ class OmnipodSettingsViewModel: ObservableObject { switch basalDeliveryState { case .active(_), .initiatingTempBasal: return true - case .tempBasal(_), .cancelingTempBasal, .suspending, .suspended(_), .resuming, .none: + default: return false } } @@ -207,7 +207,7 @@ class OmnipodSettingsViewModel: ObservableObject { return nil } - let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit()) + let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit) var didFinish: (() -> Void)? @@ -308,8 +308,8 @@ class OmnipodSettingsViewModel: ObservableObject { } } - func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { - pumpManager.runTemporaryBasalProgram(unitsPerHour: unitsPerHour, for: duration, automatic: false, completion: completion) + func runTemporaryBasalProgram(decisionId: UUID?, unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerError?) -> Void) { + pumpManager.runTemporaryBasalProgram(decisionId: decisionId, unitsPerHour: unitsPerHour, for: duration, automatic: false, completion: completion) } func saveScheduledExpirationReminder(_ selectedDate: Date?, _ completion: @escaping (Error?) -> Void) { @@ -417,13 +417,13 @@ class OmnipodSettingsViewModel: ObservableObject { func reservoirText(for level: ReservoirLevel) -> String { switch level { case .aboveThreshold: - let quantity = HKQuantity(unit: .internationalUnit(), doubleValue: Pod.maximumReservoirReading) + let quantity = LoopQuantity(unit: .internationalUnit, doubleValue: Pod.maximumReservoirReading) let thresholdString = reservoirVolumeFormatter.string(from: quantity, includeUnit: false) ?? "" let unitString = reservoirVolumeFormatter.localizedUnitStringWithPlurality(forValue: Pod.maximumReservoirReading, avoidLineBreaking: true) return String(format: LocalizedString("%1$@+ %2$@", comment: "Format string for reservoir level above max measurable threshold. (1: measurable reservoir threshold) (2: units)"), thresholdString, unitString) case .valid(let value): - let quantity = HKQuantity(unit: .internationalUnit(), doubleValue: value) + let quantity = LoopQuantity(unit: .internationalUnit, doubleValue: value) return reservoirVolumeFormatter.string(from: quantity) ?? "" } } diff --git a/OmniKitUI/Views/ExpirationReminderPickerView.swift b/OmniKitUI/Views/ExpirationReminderPickerView.swift index e7a5e5b..b9e8486 100644 --- a/OmniKitUI/Views/ExpirationReminderPickerView.swift +++ b/OmniKitUI/Views/ExpirationReminderPickerView.swift @@ -7,9 +7,9 @@ // import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit struct ExpirationReminderPickerView: View { @@ -21,7 +21,7 @@ struct ExpirationReminderPickerView: View { @State var showingHourPicker: Bool = false - var expirationDefaultFormatter = QuantityFormatter(for: .hour()) + var expirationDefaultFormatter = QuantityFormatter(for: .hour) var expirationDefaultString: String { return expirationReminderHourString(expirationReminderDefault.wrappedValue) @@ -43,16 +43,21 @@ struct ExpirationReminderPickerView: View { } } if showingHourPicker { - ResizeablePicker(selection: expirationReminderDefault, - data: Array(Self.expirationReminderHoursAllowed), - formatter: { expirationReminderHourString($0) }) + Picker(selection: expirationReminderDefault) { + ForEach(Array(Self.expirationReminderHoursAllowed), id: \.self) { value in + Text(expirationReminderHourString(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) } } } private func expirationReminderHourString(_ value: Int) -> String { if value > 0 { - return expirationDefaultFormatter.string(from: HKQuantity(unit: .hour(), doubleValue: Double(value)))! + return expirationDefaultFormatter.string(from: LoopQuantity(unit: .hour, doubleValue: Double(value)))! } else { return LocalizedString("No Reminder", comment: "Value text for no expiration reminder") } diff --git a/OmniKitUI/Views/LowReservoirReminderEditView.swift b/OmniKitUI/Views/LowReservoirReminderEditView.swift index 61fddbb..a20b9eb 100644 --- a/OmniKitUI/Views/LowReservoirReminderEditView.swift +++ b/OmniKitUI/Views/LowReservoirReminderEditView.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit import OmniKit +import SwiftUI struct LowReservoirReminderEditView: View { @@ -89,7 +89,7 @@ struct LowReservoirReminderEditView: View { } func formatValue(_ value: Int) -> String { - return insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(value))) ?? "" + return insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(value))) ?? "" } var saveButtonText: String { @@ -148,7 +148,7 @@ struct LowReservoirReminderEditView_Previews: PreviewProvider { static var previews: some View { LowReservoirReminderEditView( lowReservoirReminderValue: 20, - insulinQuantityFormatter: QuantityFormatter(for: .internationalUnit()), + insulinQuantityFormatter: QuantityFormatter(for: .internationalUnit), onSave: { (_, _) in }, onFinish: { } ) diff --git a/OmniKitUI/Views/LowReservoirReminderSetupView.swift b/OmniKitUI/Views/LowReservoirReminderSetupView.swift index d133ff7..a9befaf 100644 --- a/OmniKitUI/Views/LowReservoirReminderSetupView.swift +++ b/OmniKitUI/Views/LowReservoirReminderSetupView.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit import OmniKit +import SwiftUI struct LowReservoirReminderSetupView: View { @@ -20,10 +20,10 @@ struct LowReservoirReminderSetupView: View { public var continueButtonTapped: (() -> Void)? public var cancelButtonTapped: (() -> Void)? - var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit()) + var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit) func formatValue(_ value: Int) -> String { - return insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(value))) ?? "" + return insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(value))) ?? "" } var body: some View { diff --git a/OmniKitUI/Views/ManualTempBasalEntryView.swift b/OmniKitUI/Views/ManualTempBasalEntryView.swift index 0a77234..5cf1c65 100644 --- a/OmniKitUI/Views/ManualTempBasalEntryView.swift +++ b/OmniKitUI/Views/ManualTempBasalEntryView.swift @@ -6,11 +6,11 @@ // Copyright © 2022 LoopKit Authors. All rights reserved. // -import SwiftUI +import LoopAlgorithm import LoopKitUI import LoopKit -import HealthKit import OmniKit +import SwiftUI struct ManualTempBasalEntryView: View { @@ -51,7 +51,7 @@ struct ManualTempBasalEntryView: View { } private static let durationFormatter: QuantityFormatter = { - let quantityFormatter = QuantityFormatter(for: .hour()) + let quantityFormatter = QuantityFormatter(for: .hour) quantityFormatter.numberFormatter.minimumFractionDigits = 1 quantityFormatter.numberFormatter.maximumFractionDigits = 1 quantityFormatter.unitStyle = .long @@ -59,16 +59,16 @@ struct ManualTempBasalEntryView: View { }() private var durationUnitsLabel: some View { - Text(QuantityFormatter(for: .hour()).localizedUnitStringWithPlurality()) + Text(QuantityFormatter(for: .hour).localizedUnitStringWithPlurality()) .foregroundColor(Color(.secondaryLabel)) } func formatRate(_ rate: Double) -> String { - return ManualTempBasalEntryView.rateFormatter.string(from: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: rate)) ?? "" + return ManualTempBasalEntryView.rateFormatter.string(from: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: rate)) ?? "" } func formatDuration(_ duration: TimeInterval) -> String { - return ManualTempBasalEntryView.durationFormatter.string(from: HKQuantity(unit: .hour(), doubleValue: duration.hours)) ?? "" + return ManualTempBasalEntryView.durationFormatter.string(from: LoopQuantity(unit: .hour, doubleValue: duration.hours)) ?? "" } var body: some View { @@ -81,12 +81,23 @@ struct ManualTempBasalEntryView: View { Text(String(format: LocalizedString("%1$@ for %2$@", comment: "Summary string for temporary basal rate configuration page"), formatRate(rateEntered), formatDuration(durationEntered))) } HStack { - ResizeablePicker(selection: $rateEntered, - data: allowedRates, - formatter: { formatRate($0) }) - ResizeablePicker(selection: $durationEntered, - data: Pod.supportedTempBasalDurations, - formatter: { formatDuration($0) }) + Picker(selection: $rateEntered) { + ForEach(allowedRates, id: \.self) { value in + Text(formatRate(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) + + Picker(selection: $durationEntered) { + ForEach(Pod.supportedTempBasalDurations, id: \.self) { value in + Text(formatDuration(value)) + } + } label: { + EmptyView() + } + .pickerStyle(.wheel) } .frame(maxHeight: 162.0) .alert(isPresented: $showingMissingConfigAlert, content: { missingConfigAlert }) diff --git a/OmniKitUI/Views/NotificationSettingsView.swift b/OmniKitUI/Views/NotificationSettingsView.swift index 3f3af8e..fb563fe 100644 --- a/OmniKitUI/Views/NotificationSettingsView.swift +++ b/OmniKitUI/Views/NotificationSettingsView.swift @@ -7,9 +7,9 @@ // import SwiftUI +import LoopAlgorithm import LoopKit import LoopKitUI -import HealthKit struct NotificationSettingsView: View { @@ -29,7 +29,7 @@ struct NotificationSettingsView: View { var onSaveLowReservoirReminder: ((_ selectedValue: Int, _ completion: @escaping (_ error: Error?) -> Void) -> Void)? - var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit()) + var insulinQuantityFormatter = QuantityFormatter(for: .internationalUnit) var body: some View { RoundedCardScrollView { @@ -115,7 +115,7 @@ struct NotificationSettingsView: View { { RoundedCardValueRow( label: LocalizedString("Low Reservoir Reminder", comment: "Label for low reservoir reminder row"), - value: insulinQuantityFormatter.string(from: HKQuantity(unit: .internationalUnit(), doubleValue: Double(lowReservoirReminderValue))) ?? "", + value: insulinQuantityFormatter.string(from: LoopQuantity(unit: .internationalUnit, doubleValue: Double(lowReservoirReminderValue))) ?? "", highlightValue: false, disclosure: true) } diff --git a/OmniKitUI/Views/OmnipodSettingsView.swift b/OmniKitUI/Views/OmnipodSettingsView.swift index e629f5a..7169d1c 100644 --- a/OmniKitUI/Views/OmnipodSettingsView.swift +++ b/OmniKitUI/Views/OmnipodSettingsView.swift @@ -9,7 +9,6 @@ import SwiftUI import LoopKit import LoopKitUI -import HealthKit import OmniKit import RileyLinkBLEKit @@ -209,7 +208,7 @@ struct OmnipodSettingsView: View { .sheet(isPresented: $showManualTempBasalOptions) { ManualTempBasalEntryView( enactBasal: { rate, duration, completion in - viewModel.runTemporaryBasalProgram(unitsPerHour: rate, for: duration) { error in + viewModel.runTemporaryBasalProgram(decisionId: nil, unitsPerHour: rate, for: duration) { error in completion(error) if error == nil { showManualTempBasalOptions = false @@ -596,7 +595,7 @@ struct OmnipodSettingsView: View { func cancelManualBasal() { cancelingTempBasal = true - viewModel.runTemporaryBasalProgram(unitsPerHour: 0, for: 0) { error in + viewModel.runTemporaryBasalProgram(decisionId: nil, unitsPerHour: 0, for: 0) { error in cancelingTempBasal = false if let error = error { self.viewModel.activeAlert = .cancelManualBasalError(error)