diff --git a/Common/Models/StatusExtensionContext.swift b/Common/Models/StatusExtensionContext.swift index 9430895822..3847b9da20 100644 --- a/Common/Models/StatusExtensionContext.swift +++ b/Common/Models/StatusExtensionContext.swift @@ -287,7 +287,7 @@ struct StatusExtensionContext: RawRepresentable { } if let rawValue = rawValue["glucose"] as? [GlucoseContext.RawValue] { - glucose = rawValue.flatMap({return GlucoseContext(rawValue: $0)}) + glucose = rawValue.compactMap({return GlucoseContext(rawValue: $0)}) } if let rawValue = rawValue["predictedGlucose"] as? PredictedGlucoseContext.RawValue { @@ -311,7 +311,7 @@ struct StatusExtensionContext: RawRepresentable { activeInsulin = rawValue["activeInsulin"] as? Double if let rawValue = rawValue["targetRanges"] as? [DatedRangeContext.RawValue] { - targetRanges = rawValue.flatMap({return DatedRangeContext(rawValue: $0)}) + targetRanges = rawValue.compactMap({return DatedRangeContext(rawValue: $0)}) } if let rawValue = rawValue["temporaryOverride"] as? DatedRangeContext.RawValue { diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 2ae66a22c0..7d2d409ad7 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -236,6 +236,8 @@ 4FF4D1001E18374700846527 /* WatchContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF4D0FF1E18374700846527 /* WatchContext.swift */; }; 4FF4D1011E18375000846527 /* WatchContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF4D0FF1E18374700846527 /* WatchContext.swift */; }; 540DED971E14C75F002B2491 /* EnliteSensorDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540DED961E14C75F002B2491 /* EnliteSensorDisplayable.swift */; }; + 6AB322972070BD40001EE2C0 /* BolusThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB322962070BD40001EE2C0 /* BolusThreshold.swift */; }; + 6AB322992070BE8B001EE2C0 /* BolusThresholdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AB322982070BE8B001EE2C0 /* BolusThresholdViewController.swift */; }; 7D7076351FE06EDE004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076371FE06EDE004AC8EA /* Localizable.strings */; }; 7D70763A1FE06EDF004AC8EA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70763C1FE06EDF004AC8EA /* InfoPlist.strings */; }; 7D70763F1FE06EDF004AC8EA /* ckcomplication.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076411FE06EDF004AC8EA /* ckcomplication.strings */; }; @@ -593,6 +595,8 @@ 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+StatusExtension.swift"; sourceTree = ""; }; 4FF4D0FF1E18374700846527 /* WatchContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchContext.swift; sourceTree = ""; }; 540DED961E14C75F002B2491 /* EnliteSensorDisplayable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnliteSensorDisplayable.swift; sourceTree = ""; }; + 6AB322962070BD40001EE2C0 /* BolusThreshold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusThreshold.swift; sourceTree = ""; }; + 6AB322982070BE8B001EE2C0 /* BolusThresholdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusThresholdViewController.swift; sourceTree = ""; }; 7D68AAA91FE2DB0A00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D68AAAA1FE2DB0A00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; 7D68AAAB1FE2DB0A00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/MainInterface.strings; sourceTree = ""; }; @@ -762,6 +766,7 @@ 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */, 4D5B7A4A1D457CCA00796CA9 /* GlucoseG4.swift */, C178249D1E19B62300D9D25C /* GlucoseThreshold.swift */, + 6AB322962070BD40001EE2C0 /* BolusThreshold.swift */, 436FACED1D0BA636004E2427 /* InsulinDataSource.swift */, 436A0DA41D236A2A00104B24 /* LoopError.swift */, 43D848B11E7DF42500DADCBC /* LoopSettings.swift */, @@ -966,6 +971,7 @@ 43A51E201EB6DBDD000736CC /* ChartsTableViewController.swift */, 433EA4C31D9F71C800CD78FB /* CommandResponseViewController.swift */, C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */, + 6AB322982070BE8B001EE2C0 /* BolusThresholdViewController.swift */, 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */, 435CB6221F37967800C320C7 /* InsulinModelSettingsViewController.swift */, 437D9BA21D7BC977007245E8 /* PredictionTableViewController.swift */, @@ -1333,7 +1339,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 0930; ORGANIZATIONNAME = "LoopKit Authors"; TargetAttributes = { 43776F8B1B8022E90074EA36 = { @@ -1621,6 +1627,7 @@ 43BFF0BC1E45C80600FF19A9 /* UIColor+Loop.swift in Sources */, 43C0944A1CACCC73001F6403 /* NotificationManager.swift in Sources */, 4F08DE9D1E81D0E9006741EA /* StatusChartsManager+LoopKit.swift in Sources */, + 6AB322992070BE8B001EE2C0 /* BolusThresholdViewController.swift in Sources */, 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */, 439BED2A1E76093C00B0AED5 /* CGMManager.swift in Sources */, C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */, @@ -1639,6 +1646,7 @@ 4328E0351CFC0AE100E199AA /* WatchDataManager.swift in Sources */, 43D381621EBD9759007F8C8F /* HeaderValuesTableViewCell.swift in Sources */, 43BFF0C51E465A2D00FF19A9 /* UIColor+HIG.swift in Sources */, + 6AB322972070BD40001EE2C0 /* BolusThreshold.swift in Sources */, 4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */, 437CCAE01D285C7B0075D2C3 /* ServiceAuthentication.swift in Sources */, 4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */, @@ -2003,12 +2011,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -2068,12 +2078,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme index 2d94260fca..ab563e3f5a 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme @@ -1,6 +1,6 @@ @@ -61,7 +60,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "" selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme index b2c46e209e..301b74132b 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme @@ -1,6 +1,6 @@ + codeCoverageEnabled = "YES" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -57,7 +56,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/LoopTests.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/LoopTests.xcscheme index b74e4c3f3e..337f33d7fa 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/LoopTests.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/LoopTests.xcscheme @@ -1,6 +1,6 @@ Double { +private func targetGlucoseValue(percentEffectDuration: Double, + initialValue: Double, + minValue: Double, maxValue: Double) -> Double { + // The inflection point in time: before it we use minValue, after it we linearly blend from minValue to maxValue let useMinValueUntilPercent = 0.5 + // Allow bolus dosing below minValue during initial interval set to 15% of + // effect duration, so nominally 0.15*6*60 min = 54 min + let useInitialValueUntilPercent = 0.15 + + guard percentEffectDuration > useInitialValueUntilPercent else { + return initialValue + } + guard percentEffectDuration > useMinValueUntilPercent else { return minValue } @@ -224,6 +235,7 @@ extension Collection where Iterator.Element == GlucoseValue { private func insulinCorrection( to correctionRange: GlucoseRangeSchedule, at date: Date, + initialThreshold: HKQuantity, suspendThreshold: HKQuantity, sensitivity: HKQuantity, model: InsulinModel @@ -238,6 +250,7 @@ extension Collection where Iterator.Element == GlucoseValue { let unit = correctionRange.unit let sensitivityValue = sensitivity.doubleValue(for: unit) + let initialThresholdValue = initialThreshold.doubleValue(for: unit) let suspendThresholdValue = suspendThreshold.doubleValue(for: unit) // For each prediction above target, determine the amount of insulin necessary to correct glucose based on the modeled effectiveness of the insulin at that time @@ -246,10 +259,12 @@ extension Collection where Iterator.Element == GlucoseValue { continue } - // If any predicted value is below the suspend threshold, return immediately - guard prediction.quantity >= suspendThreshold else { + // (current Loop: If any predicted value is below the suspend threshold, return immediately) + // allow dosing above initial threshold, which is below suspend threshold for boluses + guard prediction.quantity >= initialThreshold else { return .suspend(min: prediction) } + // Update range statistics if minGlucose == nil || prediction.quantity < minGlucose!.quantity { @@ -261,9 +276,11 @@ extension Collection where Iterator.Element == GlucoseValue { let time = prediction.startDate.timeIntervalSince(date) // Compute the target value as a function of time since the dose started + // Target value initially dropped to InitialThreshold let targetValue = targetGlucoseValue( percentEffectDuration: time / model.effectDuration, - minValue: suspendThresholdValue, + initialValue: initialThresholdValue, + minValue: suspendThresholdValue, maxValue: correctionRange.value(at: prediction.startDate).averageValue ) @@ -368,20 +385,23 @@ extension Collection where Iterator.Element == GlucoseValue { let correction = self.insulinCorrection( to: correctionRange, at: date, + // for temps, initial threshold equals suspend threshold + initialThreshold: suspendThreshold ?? correctionRange.minQuantity(at: date), suspendThreshold: suspendThreshold ?? correctionRange.minQuantity(at: date), sensitivity: sensitivity.quantity(at: date), model: model ) let scheduledBasalRate = basalRates.value(at: date) - var maxBasalRate = maxBasalRate + let maxBasalRate = maxBasalRate // TODO: Allow `highBasalThreshold` to be a configurable setting - if case .aboveRange(min: let min, correcting: _, minTarget: let highBasalThreshold, units: _)? = correction, - min.quantity < highBasalThreshold - { - maxBasalRate = scheduledBasalRate - } + // (dm61 commented out lines bellow to allow high temping below minTarget but above suspend threshold) + // if case .aboveRange(min: let min, correcting: _, minTarget: let highBasalThreshold, units: _)? = correction, + // min.quantity < highBasalThreshold + // { + // maxBasalRate = scheduledBasalRate + //} let temp = correction?.asTempBasal( scheduledBasalRate: scheduledBasalRate, @@ -414,6 +434,7 @@ extension Collection where Iterator.Element == GlucoseValue { to correctionRange: GlucoseRangeSchedule, at date: Date = Date(), suspendThreshold: HKQuantity?, + bolusThreshold: HKQuantity?, sensitivity: InsulinSensitivitySchedule, model: InsulinModel, pendingInsulin: Double, @@ -423,6 +444,8 @@ extension Collection where Iterator.Element == GlucoseValue { guard let correction = self.insulinCorrection( to: correctionRange, at: date, + // for boluses, initial threshold is below suspend threshold + initialThreshold: bolusThreshold ?? correctionRange.minQuantity(at: date), suspendThreshold: suspendThreshold ?? correctionRange.minQuantity(at: date), sensitivity: sensitivity.quantity(at: date), model: model diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index fcf0f16ac9..4f4121bb4d 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -934,6 +934,7 @@ final class LoopDataManager { let recommendation = predictedGlucose.recommendedBolus( to: glucoseTargetRange, suspendThreshold: settings.suspendThreshold?.quantity, + bolusThreshold: settings.bolusThreshold?.quantity, sensitivity: insulinSensitivity, model: model, pendingInsulin: pendingInsulin, diff --git a/Loop/Models/BolusThreshold.swift b/Loop/Models/BolusThreshold.swift new file mode 100644 index 0000000000..211cc73128 --- /dev/null +++ b/Loop/Models/BolusThreshold.swift @@ -0,0 +1,51 @@ +// +// BolusThreshold.swift +// Loop +// +// Created by David Daniels on 3/25/18. +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + + + +import Foundation +import HealthKit + +struct BolusThreshold: RawRepresentable { + typealias RawValue = [String: Any] + + let value: Double + let unit: HKUnit + + public var quantity: HKQuantity { + return HKQuantity(unit: unit, doubleValue: value) + } + + public init(unit: HKUnit, value: Double) { + self.value = value + self.unit = unit + } + + init?(rawValue: RawValue) { + guard let unitsStr = rawValue["units"] as? String, let value = rawValue["value"] as? Double else { + return nil + } + self.unit = HKUnit(from: unitsStr) + self.value = value + } + + var rawValue: RawValue { + return [ + "value": value, + "units": unit.unitString + ] + } +} + + +extension BolusThreshold: Equatable { + static func ==(lhs: BolusThreshold, rhs: BolusThreshold) -> Bool { + return lhs.value == rhs.value + } +} + diff --git a/Loop/Models/LoopSettings.swift b/Loop/Models/LoopSettings.swift index b01a463683..7ad304782e 100644 --- a/Loop/Models/LoopSettings.swift +++ b/Loop/Models/LoopSettings.swift @@ -20,6 +20,8 @@ struct LoopSettings { var maximumBolus: Double? var suspendThreshold: GlucoseThreshold? = nil + + var bolusThreshold: BolusThreshold? = nil var retrospectiveCorrectionEnabled = true } @@ -63,7 +65,9 @@ extension LoopSettings: RawRepresentable { if let rawThreshold = rawValue["minimumBGGuard"] as? GlucoseThreshold.RawValue { self.suspendThreshold = GlucoseThreshold(rawValue: rawThreshold) } - + if let rawBolusThreshold = rawValue["minimumBolusGuard"] as? BolusThreshold.RawValue { + self.bolusThreshold = BolusThreshold(rawValue: rawBolusThreshold) + } if let retrospectiveCorrectionEnabled = rawValue["retrospectiveCorrectionEnabled"] as? Bool { self.retrospectiveCorrectionEnabled = retrospectiveCorrectionEnabled } @@ -80,6 +84,7 @@ extension LoopSettings: RawRepresentable { raw["maximumBasalRatePerHour"] = maximumBasalRatePerHour raw["maximumBolus"] = maximumBolus raw["minimumBGGuard"] = suspendThreshold?.rawValue + raw["minimumBolusGuard"] = bolusThreshold?.rawValue return raw } diff --git a/Loop/View Controllers/BolusThresholdViewController.swift b/Loop/View Controllers/BolusThresholdViewController.swift new file mode 100644 index 0000000000..a843266838 --- /dev/null +++ b/Loop/View Controllers/BolusThresholdViewController.swift @@ -0,0 +1,44 @@ +// +// BolusThresholdViewController.swift +// Loop +// +// Created by David Daniels on 3/26/18. +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + + + +import Foundation + +import UIKit +import LoopKit +import HealthKit + + +final class BolusThresholdTableViewController: TextFieldTableViewController { + + public let glucoseUnit: HKUnit + + init(threshold: Double?, glucoseUnit: HKUnit) { + self.glucoseUnit = glucoseUnit + + super.init(style: .grouped) + + placeholder = NSLocalizedString("Enter bolus threshold", comment: "The placeholder text instructing users to enter a bolus threshold") + keyboardType = .decimalPad + contextHelp = NSLocalizedString("When current or forecasted glucose is below the bolus threshold, Loop will not recommend a bolus.", comment: "Explanation of bolus threshold") + + unit = glucoseUnit.glucoseUnitDisplayString + + if let threshold = threshold { + value = NumberFormatter.glucoseFormatter(for: glucoseUnit).string(from: NSNumber(value: threshold)) + } + + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + diff --git a/Loop/View Controllers/RadioSelectionTableViewController.swift b/Loop/View Controllers/RadioSelectionTableViewController.swift index fc13d26427..1b119e30cb 100644 --- a/Loop/View Controllers/RadioSelectionTableViewController.swift +++ b/Loop/View Controllers/RadioSelectionTableViewController.swift @@ -78,7 +78,7 @@ extension RadioSelectionTableViewController { let vc = T() vc.selectedIndex = value.rawValue - vc.options = (0..<2).flatMap({ InsulinDataSource(rawValue: $0) }).map { String(describing: $0) } + vc.options = (0..<2).compactMap({ InsulinDataSource(rawValue: $0) }).map { String(describing: $0) } vc.contextHelp = NSLocalizedString("Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option.", comment: "Instructions on selecting an insulin data source") return vc @@ -88,7 +88,7 @@ extension RadioSelectionTableViewController { let vc = T() vc.selectedIndex = value.rawValue - vc.options = (0..<2).flatMap({ BatteryChemistryType(rawValue: $0) }).map { String(describing: $0) } + vc.options = (0..<2).compactMap({ BatteryChemistryType(rawValue: $0) }).map { String(describing: $0) } vc.contextHelp = NSLocalizedString("Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure.", comment: "Instructions on selecting battery chemistry type") return vc diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 4c530cff8b..a75260f21f 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -116,6 +116,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu fileprivate enum ConfigurationRow: Int, CaseCountable { case glucoseTargetRange = 0 case suspendThreshold + case bolusThreshold case insulinModel case basalRate case carbRatio @@ -335,6 +336,18 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu let value = valueNumberFormatter.string(from: NSNumber(value: suspendThreshold.value)) ?? "-" configCell.detailTextLabel?.text = String(format: NSLocalizedString("%1$@ %2$@", comment: "Format string for current suspend threshold. (1: value)(2: bg unit)"), value, suspendThreshold.unit.glucoseUnitDisplayString) } else { + + configCell.detailTextLabel?.text = TapToSetString + } + case .bolusThreshold: + configCell.textLabel?.text = NSLocalizedString("Bolus Threshold", comment: "The title text in settings") + + if let bolusThreshold = dataManager.loopManager.settings.bolusThreshold { + let value = valueNumberFormatter.string(from: NSNumber(value: bolusThreshold.value)) ?? "-" + configCell.detailTextLabel?.text = String(format: NSLocalizedString("%1$@ %2$@", comment: "Format string for current bolus threshold. (1: value)(2: bg unit)"), value, bolusThreshold.unit.glucoseUnitDisplayString) + } else { + + configCell.detailTextLabel?.text = TapToSetString } case .insulinModel: @@ -615,12 +628,39 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } case .insulinModel: performSegue(withIdentifier: InsulinModelSettingsViewController.className, sender: sender) + + + case .bolusThreshold: + + if let minBolusGuard = dataManager.loopManager.settings.bolusThreshold { + let vc = BolusThresholdTableViewController (threshold: minBolusGuard.value, glucoseUnit: minBolusGuard.unit) + vc.delegate = self + vc.indexPath = indexPath + vc.title = sender?.textLabel?.text + self.show(vc, sender: sender) + } else { + dataManager.loopManager.glucoseStore.preferredUnit { (unit, error) -> Void in + DispatchQueue.main.async { + if let error = error { + self.presentAlertController(with: error) + } else if let unit = unit { + let vc = BolusThresholdTableViewController (threshold: nil, glucoseUnit: unit) + vc.delegate = self + vc.indexPath = indexPath + vc.title = sender?.textLabel?.text + self.show(vc, sender: sender) + } + } + } } + } + case .devices: let vc = RileyLinkDeviceTableViewController() vc.device = dataManager.rileyLinkManager.devices[indexPath.row] show(vc, sender: sender) + case .loop: switch LoopRow(rawValue: indexPath.row)! { case .preferredInsulinDataSource: @@ -947,6 +987,14 @@ extension SettingsTableViewController: TextFieldTableViewControllerDelegate { } else { dataManager.loopManager.settings.suspendThreshold = nil } + + case .bolusThreshold: + if let controller = controller as? BolusThresholdTableViewController, + let value = controller.value, let minBolusGuard = valueNumberFormatter.number(from: value)?.doubleValue { + dataManager.loopManager.settings.bolusThreshold = BolusThreshold(unit: controller.glucoseUnit, value: minBolusGuard) + } else { + dataManager.loopManager.settings.bolusThreshold = nil + } case .maxBasal: if let value = controller.value, let rate = valueNumberFormatter.number(from: value)?.doubleValue { dataManager.loopManager.settings.maximumBasalRatePerHour = rate diff --git a/LoopUI/Managers/StatusChartsManager.swift b/LoopUI/Managers/StatusChartsManager.swift index 8019c33085..476990b437 100644 --- a/LoopUI/Managers/StatusChartsManager.swift +++ b/LoopUI/Managers/StatusChartsManager.swift @@ -417,7 +417,7 @@ public final class StatusChartsManager { frame: frame, innerFrame: innerFrame, settings: chartSettings, - layers: layers.flatMap { $0 } + layers: layers.compactMap { $0 } ) } @@ -487,7 +487,7 @@ public final class StatusChartsManager { iobLine, ] - return Chart(frame: frame, innerFrame: innerFrame, settings: chartSettings, layers: layers.flatMap { $0 }) + return Chart(frame: frame, innerFrame: innerFrame, settings: chartSettings, layers: layers.compactMap { $0 }) } public func cobChartWithFrame(_ frame: CGRect) -> Chart? { @@ -544,7 +544,7 @@ public final class StatusChartsManager { cobLine ] - return Chart(frame: frame, innerFrame: innerFrame, settings: chartSettings, layers: layers.flatMap { $0 }) + return Chart(frame: frame, innerFrame: innerFrame, settings: chartSettings, layers: layers.compactMap { $0 }) } public func doseChartWithFrame(_ frame: CGRect) -> Chart? { @@ -632,7 +632,7 @@ public final class StatusChartsManager { bolusLayer ] - return Chart(frame: frame, innerFrame: innerFrame, settings: chartSettings, layers: layers.flatMap { $0 }) + return Chart(frame: frame, innerFrame: innerFrame, settings: chartSettings, layers: layers.compactMap { $0 }) } // MARK: - Carb Effect @@ -765,7 +765,7 @@ public final class StatusChartsManager { frame: frame, innerFrame: innerFrame, settings: chartSettings, - layers: layers.flatMap { $0 } + layers: layers.compactMap { $0 } ) } @@ -893,7 +893,7 @@ public final class StatusChartsManager { frame: frame, innerFrame: coordsSpace.chartInnerFrame, settings: chartSettings, - layers: layers.flatMap { $0 } + layers: layers.compactMap { $0 } ) } diff --git a/LoopUI/Views/ChartPointsContextFillLayer.swift b/LoopUI/Views/ChartPointsContextFillLayer.swift index a2824336ce..220c5b9480 100644 --- a/LoopUI/Views/ChartPointsContextFillLayer.swift +++ b/LoopUI/Views/ChartPointsContextFillLayer.swift @@ -59,7 +59,7 @@ final class ChartPointsFillsLayer: ChartCoordsSpaceLayer { let fills: [ChartPointsFill] init?(xAxis: ChartAxis, yAxis: ChartAxis, fills: [ChartPointsFill?]) { - self.fills = fills.flatMap({ $0 }) + self.fills = fills.compactMap({ $0 }) guard fills.count > 0 else { return nil