From 335f3c30e622181c25fb9dc0d0b4c379cf886e92 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Fri, 28 Nov 2025 10:02:38 -0800 Subject: [PATCH 1/2] Sound icon fix to prevent disable misbehavior on playTestBeeps() error --- OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 364a322a..c4894179 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -264,10 +264,15 @@ struct OmniBLESettingsView: View { Button(action: { sendingTestBeepsCommand = true Task { @MainActor in + defer { + // Executed after playTestBeeps() returns or throws + sendingTestBeepsCommand = false + } do { try await viewModel.playTestBeeps() } - sendingTestBeepsCommand = false + // No errors are displayed when using the sound icon to play test beeps. + // Pod Diagnostics->Play Test Beeps will play beeps & display any errors. } }) { Image(systemName: "speaker.wave.2.circle") From e98470330af3708e1e9f92a53e252255bee0e23f Mon Sep 17 00:00:00 2001 From: marionbarker Date: Fri, 28 Nov 2025 16:46:50 -0800 Subject: [PATCH 2/2] Display and miscellaneous improvements --- OmniBLE/Localizable.xcstrings | 23 +++++--- .../PumpManagerUI/OmniBLEHUDProvider.swift | 4 +- .../ViewModels/OmniBLESettingsViewModel.swift | 52 +++++++++++++------ .../Views/OmniBLESettingsView.swift | 51 ++++++++++++------ .../PumpManagerUI/Views/PodDetailsView.swift | 14 ++--- 5 files changed, 97 insertions(+), 47 deletions(-) diff --git a/OmniBLE/Localizable.xcstrings b/OmniBLE/Localizable.xcstrings index d49f87f2..3e24b967 100644 --- a/OmniBLE/Localizable.xcstrings +++ b/OmniBLE/Localizable.xcstrings @@ -14630,6 +14630,9 @@ } } }, + "Delivery Stops" : { + "comment" : "Label for Delivery Stops row" + }, "Device Name" : { "comment" : "description label for device name pod details row", "localizations" : { @@ -30863,7 +30866,7 @@ } }, "Notification Settings" : { - "comment" : "Text for pod details disclosure row\nnavigation title for notification settings", + "comment" : "Text for Notification Settings disclosure row\nnavigation title for notification settings", "localizations" : { "ar" : { "stringUnit" : { @@ -35958,7 +35961,7 @@ } }, "Pod Activated" : { - "comment" : "Label for pod insertion row\ndescription label for activated at time pod details row", + "comment" : "Label for Pod Activated row\ndescription label for activated at time pod details row", "localizations" : { "ar" : { "stringUnit" : { @@ -36692,7 +36695,7 @@ } }, "Pod Details" : { - "comment" : "Text for pod details disclosure row\ntitle for pod details page", + "comment" : "Text for Pod Details row and page", "localizations" : { "ar" : { "stringUnit" : { @@ -36835,7 +36838,7 @@ } }, "Pod Diagnostics" : { - "comment" : "Text for pod diagnostics row\nTitle for the pod diagnostic view", + "comment" : "Title for the Pod Diagnostic row and page", "localizations" : { "ar" : { "stringUnit" : { @@ -37120,6 +37123,9 @@ } } }, + "Pod Expiration" : { + "comment" : "Label for Pod Expiration row" + }, "Pod expiration advisory alarm" : { "comment" : "Description for expiration advisory alarm", "extractionState" : "manual", @@ -37725,7 +37731,7 @@ } }, "Pod Expired" : { - "comment" : "Alert content title for podExpireImminent pod alert\nAlert content title for podExpiring pod alert\nError message for reservoir view when pod expired\nLabel for pod expiration row, past tense\nStatus highlight message for podExpired alarm.\nThe title for Pod Expired alarm notification", + "comment" : "Alert content title for podExpireImminent pod alert\nAlert content title for podExpiring pod alert\nError message for reservoir view when pod expired\nStatus highlight message for podExpired alarm.\nThe title for Pod Expired alarm notification", "localizations" : { "ar" : { "stringUnit" : { @@ -37869,6 +37875,7 @@ }, "Pod Expires" : { "comment" : "Label for pod expiration row", + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -38731,6 +38738,9 @@ } } }, + "Pod Faulted" : { + "comment" : "Label for Pod Faulted row" + }, "Pod is not in a state ready for cannula insertion." : { "comment" : "Error message when cannula insertion fails because the pod is in an unexpected state", "localizations" : { @@ -40934,6 +40944,7 @@ }, "Previous Pod" : { "comment" : "title for previous pod page", + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -41076,7 +41087,7 @@ } }, "Previous Pod Details" : { - "comment" : "Text for previous pod details row", + "comment" : "Text for Previous Pod Details row and page", "localizations" : { "ar" : { "stringUnit" : { diff --git a/OmniBLE/PumpManagerUI/OmniBLEHUDProvider.swift b/OmniBLE/PumpManagerUI/OmniBLEHUDProvider.swift index 31754f46..e4dfcb67 100644 --- a/OmniBLE/PumpManagerUI/OmniBLEHUDProvider.swift +++ b/OmniBLE/PumpManagerUI/OmniBLEHUDProvider.swift @@ -30,9 +30,7 @@ internal class OmniBLEHUDProvider: NSObject, HUDProvider { private let bluetoothProvider: BluetoothProvider private let colorPalette: LoopUIColorPalette - - private var refreshTimer: Timer? - + private let allowedInsulinTypes: [InsulinType] var visible: Bool = false { diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index 014f85c4..2c068e79 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -60,6 +60,28 @@ class OmniBLESettingsViewModel: ObservableObject { } } + var faultedAtString: String { + if let faultEventTimeSinceActivation = pumpManager.podDetails?.fault?.faultEventTimeSinceActivation, + let expiresAt = expiresAt + { + // Use expiresAt which slides with pod clock skew to compute an adjusted activatedAt + let adjustedActivatedAt = expiresAt - Pod.nominalPodLife + let faultedAt = adjustedActivatedAt + faultEventTimeSinceActivation + return dateFormatter.string(from: faultedAt) + } + return "—" + } + + var deliveryStopsAtString: String { + if let expiresAt = expiresAt { + // Use expiresAt which slides with pod clock skew to compute an adjusted activatedAt + let adjustedActivatedAt = expiresAt - Pod.nominalPodLife + let deliveryStopsAt = adjustedActivatedAt + Pod.serviceDuration + return dateFormatter.string(from: deliveryStopsAt) + } + return "—" + } + var serviceTimeRemainingString: String? { if let serviceTimeRemaining = pumpManager.podServiceTimeRemaining, let serviceTimeRemainingString = timeRemainingFormatter.string(from: serviceTimeRemaining) { return serviceTimeRemainingString @@ -548,25 +570,24 @@ extension OmniBLEPumpManager { } var basalDeliveryRate: Double? { - if let tempBasal = state.podState?.unfinalizedTempBasal, !tempBasal.isFinished() { + guard let podState = state.podState, !podState.isFaulted && !podState.isSuspended else { + return nil // no pod, pod is faulted, or pod is suspended + } + if let tempBasal = podState.unfinalizedTempBasal, !tempBasal.isFinished() { + // return the current unfinalized temp basal rate return tempBasal.rate - } else { - switch state.podState?.suspendState { - case .resumed: - var calendar = Calendar(identifier: .gregorian) - calendar.timeZone = state.timeZone - return state.basalSchedule.currentRate(using: calendar, at: dateGenerator()) - case .suspended, .none: - return nil - } } + // return the scheduled basal rate for the current time + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = state.timeZone + return state.basalSchedule.currentRate(using: calendar, at: dateGenerator()) } - + fileprivate var podServiceTimeRemaining : TimeInterval? { - guard let podTimeRemaining = podTimeRemaining else { - return nil; + guard let podTimeRemaining = podTimeRemaining, state.podState?.isFaulted == false else { + return nil } - return max(0, Pod.serviceDuration - Pod.nominalPodLife + podTimeRemaining); + return max(0, Pod.serviceDuration - Pod.nominalPodLife + podTimeRemaining) } private func podDetails(fromPodState podState: PodState, andDeviceName deviceName: String?) -> PodDetails { @@ -578,10 +599,9 @@ extension OmniBLEPumpManager { deviceName: deviceName, totalDelivery: podState.lastInsulinMeasurements?.delivered, lastStatus: podState.lastInsulinMeasurements?.validTime, - fault: podState.fault?.faultEventCode, + fault: podState.fault, activatedAt: podState.activatedAt, activeTime: podState.activeTime, - pdmRef: podState.fault?.pdmRef ) } diff --git a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index c4894179..e85637b0 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -354,45 +354,61 @@ struct OmniBLESettingsView: View { Section() { HStack { - FrameworkLocalText("Pod Activated", comment: "Label for pod insertion row") + FrameworkLocalText("Pod Activated", comment: "Label for Pod Activated row") Spacer() Text(self.viewModel.activatedAtString) .foregroundColor(Color.secondary) } HStack { - if let expiresAt = viewModel.expiresAt, expiresAt < Date() { - FrameworkLocalText("Pod Expired", comment: "Label for pod expiration row, past tense") - } else { - FrameworkLocalText("Pod Expires", comment: "Label for pod expiration row") - } + FrameworkLocalText("Pod Expiration", comment: "Label for Pod Expiration row") Spacer() Text(self.viewModel.expiresAtString) .foregroundColor(Color.secondary) } + HStack { + if viewModel.podDetails?.fault != nil { + FrameworkLocalText("Pod Faulted", comment: "Label for Pod Faulted row") + Spacer() + Text(self.viewModel.faultedAtString) + .foregroundColor(Color.secondary) + } else if viewModel.podOk { + FrameworkLocalText("Delivery Stops", comment: "Label for Delivery Stops row") + Spacer() + Text(self.viewModel.deliveryStopsAtString) + .foregroundColor(Color.secondary) + } + } + + let localizedPodDetailsStr = LocalizedString("Pod Details", comment: "Text for Pod Details row and page") if let podDetails = self.viewModel.podDetails { - NavigationLink(destination: PodDetailsView(podDetails: podDetails, title: LocalizedString("Pod Details", comment: "title for pod details page"))) { - FrameworkLocalText("Pod Details", comment: "Text for pod details disclosure row") + NavigationLink(destination: PodDetailsView(podDetails: podDetails, + title: localizedPodDetailsStr)) + { + Text(localizedPodDetailsStr) .foregroundColor(Color.primary) } } else { HStack { - FrameworkLocalText("Pod Details", comment: "Text for pod details disclosure row") + Text(localizedPodDetailsStr) Spacer() Text("—") .foregroundColor(Color.secondary) } } + let localizedPreviousPodDetailsStr = LocalizedString("Previous Pod Details", comment: "Text for Previous Pod Details row and page") if let previousPodDetails = viewModel.previousPodDetails { - NavigationLink(destination: PodDetailsView(podDetails: previousPodDetails, title: LocalizedString("Previous Pod", comment: "title for previous pod page"))) { - FrameworkLocalText("Previous Pod Details", comment: "Text for previous pod details row") + NavigationLink(destination: PodDetailsView(podDetails: previousPodDetails, + title: localizedPreviousPodDetailsStr)) + { + Text(localizedPreviousPodDetailsStr) .foregroundColor(Color.primary) } } else { HStack { - FrameworkLocalText("Previous Pod Details", comment: "Text for previous pod details row") + Text(localizedPreviousPodDetailsStr) Spacer() Text("—") .foregroundColor(Color.secondary) @@ -421,7 +437,8 @@ struct OmniBLESettingsView: View { onSaveScheduledExpirationReminder: self.viewModel.saveScheduledExpirationReminder, onSaveLowReservoirReminder: self.viewModel.saveLowReservoirReminder)) { - FrameworkLocalText("Notification Settings", comment: "Text for pod details disclosure row").foregroundColor(Color.primary) + FrameworkLocalText("Notification Settings", comment: "Text for Notification Settings disclosure row") + .foregroundColor(Color.primary) } NavigationLink(destination: BeepPreferenceSelectionView(initialValue: viewModel.beepPreference, onSave: viewModel.setConfirmationBeeps)) { HStack { @@ -443,7 +460,8 @@ struct OmniBLESettingsView: View { } NavigationLink(destination: InsulinTypeSetting(initialValue: viewModel.insulinType, supportedInsulinTypes: supportedInsulinTypes, allowUnsetInsulinType: false, didChange: viewModel.didChangeInsulinType)) { HStack { - FrameworkLocalText("Insulin Type", comment: "Text for insulin type navigation link").foregroundColor(Color.primary) + FrameworkLocalText("Insulin Type", comment: "Text for insulin type navigation link") + .foregroundColor(Color.primary) if let currentTitle = viewModel.insulinType?.brandName { Spacer() Text(currentTitle) @@ -484,13 +502,14 @@ struct OmniBLESettingsView: View { } Section() { + let localizedPodDiagnosticsStr = LocalizedString("Pod Diagnostics", comment: "Title for the Pod Diagnostic row and page") NavigationLink(destination: PodDiagnosticsView( - title: LocalizedString("Pod Diagnostics", comment: "Title for the pod diagnostic view"), + title: localizedPodDiagnosticsStr, diagnosticCommands: viewModel.diagnosticCommands, podOk: viewModel.podOk, noPod: viewModel.noPod)) { - FrameworkLocalText("Pod Diagnostics", comment: "Text for pod diagnostics row") + Text(localizedPodDiagnosticsStr) .foregroundColor(Color.primary) } } diff --git a/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift b/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift index 88d9b69f..6150b932 100644 --- a/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift +++ b/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift @@ -17,10 +17,9 @@ public struct PodDetails { var deviceName: String? var totalDelivery: Double? var lastStatus: Date? - var fault: FaultEventCode? + var fault: DetailedStatus? var activatedAt: Date? var activeTime: TimeInterval? - var pdmRef: String? } struct PodDetailsView: View { @@ -96,13 +95,15 @@ struct PodDetailsView: View { row(LocalizedString("Firmware Version", comment: "description label for firmware version pod details row"), value: podDetails.firmwareVersion) row(LocalizedString("BLE Firmware Version", comment: "description label for ble firmware version pod details row"), value: podDetails.bleFirmwareVersion) row(LocalizedString("Total Delivery", comment: "description label for total delivery pod details row"), value: totalDeliveryText) - if let activeTime = podDetails.activeTime, let activatedAt = podDetails.activatedAt { + if let activatedAt = podDetails.activatedAt { row(LocalizedString("Pod Activated", comment: "description label for activated at time pod details row"), value: dateFormatter.string(from: activatedAt)) + } + if let activeTime = podDetails.activeTime { row(LocalizedString("Active Time", comment: "description label for active time pod details row"), value: activeTimeText(activeTime)) } else { row(LocalizedString("Last Status", comment: "description label for last status date pod details row"), value: lastStatusText) } - if let fault = podDetails.fault, let pdmRef = podDetails.pdmRef { + if let fault = podDetails.fault, let pdmRef = fault.pdmRef { Section { VStack(alignment: .leading) { HStack { @@ -111,7 +112,8 @@ struct PodDetailsView: View { Text(LocalizedString("Pod Fault Details", comment: "description label for pod fault details")) .fontWeight(.semibold) }.padding(.vertical, 4) - Text(String(format: LocalizedString("Internal Pod fault code %1$03d\n%2$@\nRef: %3$@\n", comment: "The format string for the pod fault info: (1: fault code) (2: fault description) (3: pdm ref string)"), fault.rawValue, fault.faultDescription, pdmRef)) + let faultCode = fault.faultEventCode + Text(String(format: LocalizedString("Internal Pod fault code %1$03d\n%2$@\nRef: %3$@\n", comment: "The format string for the pod fault info: (1: fault code) (2: fault description) (3: pdm ref string)"), faultCode.rawValue, faultCode.faultDescription, pdmRef)) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.secondary) } @@ -124,6 +126,6 @@ struct PodDetailsView: View { struct PodDetailsView_Previews: PreviewProvider { static var previews: some View { - PodDetailsView(podDetails: PodDetails(lotNumber: 123456789, sequenceNumber: 1234567, firmwareVersion: "4.3.2", bleFirmwareVersion: "1.2.3", deviceName: "DashPreviewPod", totalDelivery: 99, lastStatus: Date(), fault: FaultEventCode(rawValue: 064), activatedAt: Date().addingTimeInterval(.days(2)), pdmRef: "19-02448-09951-064"), title: "Pod Details") + PodDetailsView(podDetails: PodDetails(lotNumber: 123456789, sequenceNumber: 1234567, firmwareVersion: "4.3.2", bleFirmwareVersion: "1.2.3", deviceName: "DashPreviewPod", totalDelivery: 99, lastStatus: Date(), fault: nil, activatedAt: Date().addingTimeInterval(.days(2))), title: "Pod Details") } }