diff --git a/LoopKit/GlucoseRangeSchedule.swift b/LoopKit/GlucoseRangeSchedule.swift index ed3a49411..ecf87a4d3 100644 --- a/LoopKit/GlucoseRangeSchedule.swift +++ b/LoopKit/GlucoseRangeSchedule.swift @@ -10,7 +10,7 @@ import Foundation import HealthKit import LoopAlgorithm -public struct DoubleRange { +public struct DoubleRange: Sendable { public let minValue: Double public let maxValue: Double diff --git a/LoopKit/TemporaryScheduleOverride.swift b/LoopKit/TemporaryScheduleOverride.swift index c3b2a7b1c..51ca099bd 100644 --- a/LoopKit/TemporaryScheduleOverride.swift +++ b/LoopKit/TemporaryScheduleOverride.swift @@ -10,20 +10,20 @@ import Foundation import HealthKit import LoopAlgorithm -public struct TemporaryScheduleOverride: Hashable { - public enum Context: Hashable { +public struct TemporaryScheduleOverride: Hashable, Sendable { + public enum Context: Hashable, Sendable { case preMeal case legacyWorkout case preset(TemporaryScheduleOverridePreset) case custom } - public enum EnactTrigger: Hashable { + public enum EnactTrigger: Hashable, Sendable { case local case remote(String) } - public enum Duration: Hashable, Comparable { + public enum Duration: Hashable, Comparable, Sendable { case finite(TimeInterval) case indefinite diff --git a/LoopKit/TemporaryScheduleOverrideHistory.swift b/LoopKit/TemporaryScheduleOverrideHistory.swift index 191623e42..0f590e10f 100644 --- a/LoopKit/TemporaryScheduleOverrideHistory.swift +++ b/LoopKit/TemporaryScheduleOverrideHistory.swift @@ -9,7 +9,7 @@ import Foundation import SwiftData -public enum End: Equatable, Hashable, Codable { +public enum End: Equatable, Hashable, Codable, Sendable { case natural case early(Date) case deleted // Ended before started @@ -134,9 +134,10 @@ public final class TemporaryScheduleOverrideHistory { } public func activeOverride(at date: Date) -> TemporaryScheduleOverride? { - recentEvents.first { event in + let active = recentEvents.filter({$0.override.actualEnd != .deleted}).first { event in event.override.isActive(at: date) - }?.override + } + return active?.override } private var lastUndeletedEvent: OverrideEvent? { diff --git a/LoopKit/TemporaryScheduleOverridePreset.swift b/LoopKit/TemporaryScheduleOverridePreset.swift index db3f3ce6b..fe050ac7f 100644 --- a/LoopKit/TemporaryScheduleOverridePreset.swift +++ b/LoopKit/TemporaryScheduleOverridePreset.swift @@ -9,7 +9,7 @@ import Foundation -public struct TemporaryScheduleOverridePreset: Hashable { +public struct TemporaryScheduleOverridePreset: Hashable, Sendable { public let id: UUID public var symbol: String public var name: String diff --git a/LoopKit/TemporaryScheduleOverrideSettings.swift b/LoopKit/TemporaryScheduleOverrideSettings.swift index 5bd404a44..b09ad24a3 100644 --- a/LoopKit/TemporaryScheduleOverrideSettings.swift +++ b/LoopKit/TemporaryScheduleOverrideSettings.swift @@ -9,7 +9,7 @@ import HealthKit import LoopAlgorithm -public struct TemporaryScheduleOverrideSettings: Hashable { +public struct TemporaryScheduleOverrideSettings: Hashable, Sendable { private var targetRangeInMgdl: DoubleRange? public var insulinNeedsScaleFactor: Double? diff --git a/LoopKit/TherapySettings.swift b/LoopKit/TherapySettings.swift index b549241ee..555827a54 100644 --- a/LoopKit/TherapySettings.swift +++ b/LoopKit/TherapySettings.swift @@ -67,30 +67,6 @@ public struct TherapySettings: Equatable { self.basalRateSchedule = basalRateSchedule self.defaultRapidActingModel = defaultRapidActingModel } - - public var preMealGuardrail: Guardrail { - if let scheduleRange = glucoseTargetRangeSchedule?.scheduleRange() { - return Guardrail.correctionRangeOverride( - for: .preMeal, - correctionRangeScheduleRange: scheduleRange, - suspendThreshold: suspendThreshold - ) - } else { - return Guardrail.correctionRange - } - } - - public var legacyWorkoutPresetGuardrail: Guardrail { - if let scheduleRange = glucoseTargetRangeSchedule?.scheduleRange() { - return Guardrail.correctionRangeOverride( - for: .workout, - correctionRangeScheduleRange: scheduleRange, - suspendThreshold: suspendThreshold - ) - } else { - return Guardrail.correctionRange - } - } } extension TherapySettings: Codable { @@ -174,6 +150,7 @@ extension TherapySettings { return (basalRate, carbRatio, isf) } + } extension TherapySettings { diff --git a/LoopKitUI/OnboardingUI.swift b/LoopKitUI/OnboardingUI.swift index 5aaf9f40b..68ba1ab9f 100644 --- a/LoopKitUI/OnboardingUI.swift +++ b/LoopKitUI/OnboardingUI.swift @@ -121,7 +121,7 @@ public protocol ServiceProvider: AnyObject { var availableServices: [ServiceDescriptor] { get } } -public protocol TherapySettingsProvider { +public protocol OnboardingTherapySettingsProvider { var onboardingTherapySettings: TherapySettings { get } } @@ -129,7 +129,7 @@ public protocol SupportProvider: AnyObject { var availableSupports: [SupportUI] { get } } -public protocol OnboardingProvider: NotificationAuthorizationProvider, HealthStoreAuthorizationProvider, BluetoothProvider, CGMManagerProvider, PumpManagerProvider, StatefulPluggableProvider, ServiceProvider, TherapySettingsProvider, SupportProvider, PluginHost { +public protocol OnboardingProvider: NotificationAuthorizationProvider, HealthStoreAuthorizationProvider, BluetoothProvider, CGMManagerProvider, PumpManagerProvider, StatefulPluggableProvider, ServiceProvider, OnboardingTherapySettingsProvider, SupportProvider, PluginHost { var allowDebugFeatures: Bool { get } // NOTE: DEBUG FEATURES - DEBUG AND TEST ONLY } diff --git a/LoopKitUI/View Controllers/DateAndDurationTableViewController.swift b/LoopKitUI/View Controllers/DateAndDurationTableViewController.swift index 2c3e986e2..7b5074010 100644 --- a/LoopKitUI/View Controllers/DateAndDurationTableViewController.swift +++ b/LoopKitUI/View Controllers/DateAndDurationTableViewController.swift @@ -48,6 +48,7 @@ public class DateAndDurationTableViewController: UITableViewController { public func onSave(_ completion: @escaping (InputMode) -> Void) { let saveBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(save)) navigationItem.rightBarButtonItem = saveBarButtonItem + navigationItem.rightBarButtonItem?.accessibilityIdentifier = "button_Save" self.completion = completion } diff --git a/LoopKitUI/Views/ChartTableViewCell.swift b/LoopKitUI/Views/ChartTableViewCell.swift index 986865e59..58ddda800 100644 --- a/LoopKitUI/Views/ChartTableViewCell.swift +++ b/LoopKitUI/Views/ChartTableViewCell.swift @@ -40,8 +40,6 @@ public final class ChartTableViewCell: UITableViewCell { public var doesNavigate: Bool = true { didSet { rightArrowHint?.isHidden = !doesNavigate - rightArrowHint?.accessibilityIdentifier = - "image_navigateToGlucoseDetails_\(doesNavigate)" } } @@ -70,6 +68,7 @@ public final class ChartTableViewCell: UITableViewCell { public func setTitleLabelText(label: String?) { titleLabel?.text = label titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) + titleLabel?.accessibilityIdentifier = "chartTitleText_\(label!.replacing(" ", with: ""))" } public func setTitleLabelText(label: NSAttributedString?) { diff --git a/MockKitUI/View Controllers/IssueAlertTableViewController.swift b/MockKitUI/View Controllers/IssueAlertTableViewController.swift index 716c30113..5a6b9d159 100644 --- a/MockKitUI/View Controllers/IssueAlertTableViewController.swift +++ b/MockKitUI/View Controllers/IssueAlertTableViewController.swift @@ -107,6 +107,7 @@ final class IssueAlertTableViewController: UITableViewController { tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className) let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:))) + button.accessibilityIdentifier = "button_done" navigationItem.setRightBarButton(button, animated: false) } diff --git a/MockKitUI/View Controllers/MockCGMManagerSettingsViewController.swift b/MockKitUI/View Controllers/MockCGMManagerSettingsViewController.swift index 049673076..a28596ab4 100644 --- a/MockKitUI/View Controllers/MockCGMManagerSettingsViewController.swift +++ b/MockKitUI/View Controllers/MockCGMManagerSettingsViewController.swift @@ -224,6 +224,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { switch ModelRow(rawValue: indexPath.row)! { case .constant: cell.textLabel?.text = "Constant" + cell.accessibilityIdentifier = "cell_Constant" if case .constant(let glucose) = cgmManager.dataSource.model { cell.detailTextLabel?.text = quantityFormatter.string(from: glucose) cell.accessoryType = .checkmark @@ -232,6 +233,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { } case .sineCurve: cell.textLabel?.text = "Sine Curve" + cell.accessibilityIdentifier = "cell_SineCurve" if case .sineCurve(parameters: (baseGlucose: let baseGlucose, amplitude: let amplitude, period: _, referenceDate: _)) = cgmManager.dataSource.model { if let baseGlucoseText = quantityFormatter.numberFormatter.string(from: baseGlucose.doubleValue(for: glucoseUnit)), let amplitudeText = quantityFormatter.string(from: amplitude) { @@ -263,6 +265,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { } case .frequency: cell.textLabel?.text = "Measurement Frequency" + cell.accessibilityIdentifier = "cell_MeasurementFrequency" cell.detailTextLabel?.text = cgmManager.dataSource.dataPointFrequency.localizedDescription cell.accessoryType = .disclosureIndicator } @@ -277,6 +280,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { cell.onToggle = { [weak self] isOn in self?.cgmManager.mockSensorState.glucoseAlertingEnabled = isOn } + cell.switch?.accessibilityIdentifier = "switch_GlucoseValueAlerting" cell.selectionStyle = .none return cell case .cgmLowerLimit: @@ -284,15 +288,19 @@ final class MockCGMManagerSettingsViewController: UITableViewController { cell.detailTextLabel?.text = quantityFormatter.string(from: cgmManager.mockSensorState.cgmLowerLimit) case .urgentLowGlucoseThreshold: cell.textLabel?.text = "Urgent Low Glucose Threshold" + cell.accessibilityIdentifier = "cell_UrgentLowGlucoseThreshold" cell.detailTextLabel?.text = quantityFormatter.string(from: cgmManager.mockSensorState.urgentLowGlucoseThreshold) case .lowGlucoseThreshold: cell.textLabel?.text = "Low Glucose Threshold" + cell.accessibilityIdentifier = "cell_LowGlucoseThreshold" cell.detailTextLabel?.text = quantityFormatter.string(from: cgmManager.mockSensorState.lowGlucoseThreshold) case .highGlucoseThreshold: cell.textLabel?.text = "High Glucose Threshold" + cell.accessibilityIdentifier = "cell_HighGlucoseThreshold" cell.detailTextLabel?.text = quantityFormatter.string(from: cgmManager.mockSensorState.highGlucoseThreshold) case .cgmUpperLimit: cell.textLabel?.text = "CGM Upper Limit" + cell.accessibilityIdentifier = "cell_CgmUpperLimit" cell.detailTextLabel?.text = quantityFormatter.string(from: cgmManager.mockSensorState.cgmUpperLimit) } cell.accessoryType = .disclosureIndicator @@ -302,6 +310,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { switch EffectsRow(rawValue: indexPath.row)! { case .noise: cell.textLabel?.text = "Glucose Noise" + cell.accessibilityIdentifier = "cell_GlucoseNoise" if let maximumDeltaMagnitude = cgmManager.dataSource.effects.glucoseNoise { cell.detailTextLabel?.text = quantityFormatter.string(from: maximumDeltaMagnitude) } else { @@ -363,9 +372,11 @@ final class MockCGMManagerSettingsViewController: UITableViewController { switch HistoryRow(rawValue: indexPath.row)! { case .trend: cell.textLabel?.text = "Trend" + cell.accessibilityIdentifier = "cell_Trend" cell.detailTextLabel?.text = cgmManager.mockSensorState.trendType?.symbol case .backfill: cell.textLabel?.text = "Backfill Glucose" + cell.accessibilityIdentifier = "cell_BackfillGlucose" } cell.accessoryType = .disclosureIndicator return cell @@ -374,6 +385,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { switch AlertsRow(rawValue: indexPath.row)! { case .issueAlert: cell.textLabel?.text = "Issue Alerts" + cell.accessibilityIdentifier = "cell_IssueAlerts" cell.accessoryType = .disclosureIndicator } return cell @@ -382,6 +394,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { switch LifecycleProgressRow(rawValue: indexPath.row)! { case .percentComplete: cell.textLabel?.text = "Percent Completed" + cell.accessibilityIdentifier = "cell_PercentCompleted" if let percentCompleted = cgmManager.mockSensorState.cgmLifecycleProgress?.percentComplete { cell.detailTextLabel?.text = "\(Int(round(percentCompleted * 100)))%" } else { @@ -389,6 +402,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { } case .warningThreshold: cell.textLabel?.text = "Warning Threshold" + cell.accessibilityIdentifier = "cell_WarningThreshold" if let warningThreshold = cgmManager.mockSensorState.progressWarningThresholdPercentValue { cell.detailTextLabel?.text = "\(Int(round(warningThreshold * 100)))%" } else { @@ -396,6 +410,7 @@ final class MockCGMManagerSettingsViewController: UITableViewController { } case .criticalThreshold: cell.textLabel?.text = "Critical Threshold" + cell.accessibilityIdentifier = "cell_CriticalThreshold" if let criticalThreshold = cgmManager.mockSensorState.progressCriticalThresholdPercentValue { cell.detailTextLabel?.text = "\(Int(round(criticalThreshold * 100)))%" } else { @@ -577,6 +592,8 @@ final class MockCGMManagerSettingsViewController: UITableViewController { vc.glucoseTrend = cgmManager.mockSensorState.trendType vc.title = "Glucose Trend" vc.glucoseTrendDelegate = self + vc.glucoseTrend?.image?.accessibilityIdentifier = + "cell_\(vc.glucoseTrend?.localizedDescription.replacingOccurrences(of: " ", with: "") ?? "")" show(vc, sender: sender) case .backfill: let vc = DateAndDurationTableViewController() diff --git a/MockKitUI/View Controllers/SineCurveParametersTableViewController.swift b/MockKitUI/View Controllers/SineCurveParametersTableViewController.swift index ba85c7ea9..ed2232c5e 100644 --- a/MockKitUI/View Controllers/SineCurveParametersTableViewController.swift +++ b/MockKitUI/View Controllers/SineCurveParametersTableViewController.swift @@ -130,15 +130,19 @@ final class SineCurveParametersTableViewController: UITableViewController { switch Row(rawValue: indexPath.row)! { case .baseGlucose: cell.textLabel?.text = "Base Glucose" + cell.accessibilityIdentifier = "cell_BaseGlucose" cell.detailTextLabel?.text = baseGlucose.map(formatGlucose) ?? SettingsTableViewCell.NoValueString case .amplitude: cell.textLabel?.text = "Amplitude" + cell.accessibilityIdentifier = "cell_Amplitude" cell.detailTextLabel?.text = amplitude.map(formatGlucose) ?? SettingsTableViewCell.NoValueString case .period: cell.textLabel?.text = "Period" + cell.accessibilityIdentifier = "cell_Period" cell.detailTextLabel?.text = period.flatMap(durationFormatter.string(from:)) ?? SettingsTableViewCell.NoValueString case .referenceDate: cell.textLabel?.text = "Reference Date" + cell.accessibilityIdentifier = "cell_ReferenceDate" cell.detailTextLabel?.text = referenceDate.map(dateFormatter.string(from:)) ?? SettingsTableViewCell.NoValueString } diff --git a/MockKitUI/Views/MockCGMManagerSettingsView.swift b/MockKitUI/Views/MockCGMManagerSettingsView.swift index 8b66213c2..7025fb9c0 100644 --- a/MockKitUI/Views/MockCGMManagerSettingsView.swift +++ b/MockKitUI/Views/MockCGMManagerSettingsView.swift @@ -89,6 +89,7 @@ struct MockCGMManagerSettingsView: View { .aspectRatio(contentMode: ContentMode.fit) .frame(maxHeight: 70) .frame(width: 70) + .accessibilityIdentifier("image_CgmSimulator") } }