From 5ea6f8d463cddf3a5102d56031873742906c24a0 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 5 Mar 2017 20:08:32 -0600 Subject: [PATCH] Last loop completed and battery level restored from userDefaults on app launch. Refactoring colors out of LoopUI to allow for App/Extension customization --- .../Extensions/NumberFormatter.swift | 2 +- Common/Extensions/UIColor+HIG.swift | 52 ++++ Common/Extensions/UIColor.swift | 32 ++ Common/Models/StatusExtensionContext.swift | 286 ++++++++++++------ .../Base.lproj/MainInterface.storyboard | 17 +- Loop Status Extension/StateColorPalette.swift | 16 + .../StatusViewController.swift | 63 ++-- Loop Status Extension/UIColor+Widget.swift | 23 ++ Loop.xcodeproj/project.pbxproj | 102 ++++--- Loop/Extensions/StateColorPalette.swift | 16 + Loop/Extensions/UIColor+Loop.swift | 20 ++ Loop/Managers/DeviceDataManager.swift | 10 +- Loop/Managers/LoopDataManager.swift | 3 +- .../Managers/StatusExtensionDataManager.swift | 66 ++-- .../StatusTableViewController.swift | 75 ++--- LoopUI/Extensions/UIColor.swift | 78 ----- LoopUI/HUDView.xib | 27 +- LoopUI/Models/StateColorPalette.swift | 24 ++ LoopUI/Views/BasalRateHUDView.swift | 11 +- LoopUI/Views/BasalStateView.swift | 16 +- LoopUI/Views/BatteryLevelHUDView.swift | 26 +- LoopUI/Views/GlucoseHUDView.swift | 36 ++- LoopUI/Views/LevelHUDView.swift | 52 ++++ LoopUI/Views/LoopCompletionHUDView.swift | 46 ++- LoopUI/Views/LoopStateView.swift | 40 +-- LoopUI/Views/ReservoirVolumeHUDView.swift | 13 +- 26 files changed, 728 insertions(+), 424 deletions(-) rename LoopUI/Extensions/NSNumberFormatter.swift => Common/Extensions/NumberFormatter.swift (94%) create mode 100644 Common/Extensions/UIColor+HIG.swift create mode 100644 Common/Extensions/UIColor.swift create mode 100644 Loop Status Extension/StateColorPalette.swift create mode 100644 Loop Status Extension/UIColor+Widget.swift create mode 100644 Loop/Extensions/StateColorPalette.swift create mode 100644 Loop/Extensions/UIColor+Loop.swift delete mode 100644 LoopUI/Extensions/UIColor.swift create mode 100644 LoopUI/Models/StateColorPalette.swift create mode 100644 LoopUI/Views/LevelHUDView.swift diff --git a/LoopUI/Extensions/NSNumberFormatter.swift b/Common/Extensions/NumberFormatter.swift similarity index 94% rename from LoopUI/Extensions/NSNumberFormatter.swift rename to Common/Extensions/NumberFormatter.swift index cab0eaf927..fced6d67ee 100644 --- a/LoopUI/Extensions/NSNumberFormatter.swift +++ b/Common/Extensions/NumberFormatter.swift @@ -11,7 +11,7 @@ import HealthKit extension NumberFormatter { - public static func glucoseFormatter(for unit: HKUnit) -> NumberFormatter { + static func glucoseFormatter(for unit: HKUnit) -> NumberFormatter { let numberFormatter = NumberFormatter() numberFormatter.numberStyle = .decimal diff --git a/Common/Extensions/UIColor+HIG.swift b/Common/Extensions/UIColor+HIG.swift new file mode 100644 index 0000000000..8c3c382ce8 --- /dev/null +++ b/Common/Extensions/UIColor+HIG.swift @@ -0,0 +1,52 @@ +// +// UIColor+HIG.swift +// Naterade +// +// Created by Nathan Racklyeft on 1/23/16. +// Copyright © 2016 Nathan Racklyeft. All rights reserved. +// + +import UIKit + + +extension UIColor { + // MARK: - HIG colors + // See: https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/ + + static func HIGTealBlueColor() -> UIColor { + return UIColor(red: 90 / 255, green: 200 / 255, blue: 250 / 255, alpha: 1) + } + + static func HIGYellowColor() -> UIColor { + return UIColor(red: 1, green: 204 / 255, blue: 0, alpha: 1) + } + + static func HIGOrangeColor() -> UIColor { + return UIColor(red: 1, green: 149 / 255, blue: 0 / 255, alpha: 1) + } + + static func HIGPinkColor() -> UIColor { + return UIColor(red: 1, green: 45 / 255, blue: 85 / 255, alpha: 1) + } + + static func HIGBlueColor() -> UIColor { + return UIColor(red: 0, green: 122 / 255, blue: 1, alpha: 1) + } + + static func HIGGreenColor() -> UIColor { + return UIColor(red: 76 / 255, green: 217 / 255, blue: 100 / 255, alpha: 1) + } + + static func HIGRedColor() -> UIColor { + return UIColor(red: 1, green: 59 / 255, blue: 48 / 255, alpha: 1) + } + + static func HIGPurpleColor() -> UIColor { + return UIColor(red: 88 / 255, green: 86 / 255, blue: 214 / 255, alpha: 1) + } + + static func HIGGrayColor() -> UIColor { + return UIColor(red: 142 / 255, green: 143 / 255, blue: 147 / 255, alpha: 1) + } + +} diff --git a/Common/Extensions/UIColor.swift b/Common/Extensions/UIColor.swift new file mode 100644 index 0000000000..9087d2d41c --- /dev/null +++ b/Common/Extensions/UIColor.swift @@ -0,0 +1,32 @@ +// +// UIColor.swift +// Naterade +// +// Created by Nathan Racklyeft on 1/23/16. +// Copyright © 2016 Nathan Racklyeft. All rights reserved. +// + +import UIKit + + +extension UIColor { + @nonobjc static var tintColor: UIColor? = nil + + @nonobjc static let secondaryLabelColor = UIColor.HIGGrayColor() + + @nonobjc static let cellBackgroundColor = UIColor(white: 239 / 255, alpha: 1) + + @nonobjc static let gridColor = UIColor(white: 193 / 255, alpha: 1) + + @nonobjc static let IOBTintColor = UIColor.HIGOrangeColor() + + @nonobjc static let COBTintColor = UIColor(red: 99 / 255, green: 218 / 255, blue: 56 / 255, alpha: 1) + + @nonobjc static let agingColor = UIColor.HIGYellowColor() + + @nonobjc static let staleColor = UIColor.HIGRedColor() + + @nonobjc static let unknownColor = UIColor(red: 198 / 255, green: 199 / 255, blue: 201 / 255, alpha: 1) + + @nonobjc static let deleteColor = UIColor.HIGRedColor() +} diff --git a/Common/Models/StatusExtensionContext.swift b/Common/Models/StatusExtensionContext.swift index be37aa25d4..526517ae82 100644 --- a/Common/Models/StatusExtensionContext.swift +++ b/Common/Models/StatusExtensionContext.swift @@ -36,126 +36,218 @@ struct SensorDisplayableContext: SensorDisplayable { } struct GlucoseContext { - let quantity: Double + let value: Double + let unit: HKUnit let startDate: Date - let sensor: SensorDisplayable? + let sensor: SensorDisplayableContext? + + var quantity: HKQuantity { + return HKQuantity(unit: unit, doubleValue: value) + } } -final class StatusExtensionContext: RawRepresentable { +extension ReservoirContext: RawRepresentable { typealias RawValue = [String: Any] - private let version = 1 - - var preferredUnitString: String? - var latestGlucose: GlucoseContext? - var reservoir: ReservoirContext? - var loop: LoopContext? - var netBasal: NetBasalContext? - var batteryPercentage: Double? - var eventualGlucose: Double? - - init() { } - - required init?(rawValue: RawValue) { - let raw = rawValue - - if let preferredString = raw["preferredUnitString"] as? String, - let latestValue = raw["latestGlucose_value"] as? Double, - let startDate = raw["latestGlucose_startDate"] as? Date { - - var sensor: SensorDisplayableContext? = nil - if let state = raw["latestGlucose_sensor_isStateValid"] as? Bool, - let desc = raw["latestGlucose_sensor_stateDescription"] as? String, - let local = raw["latestGlucose_sensor_isLocal"] as? Bool { - - var glucoseTrend: GlucoseTrend? - if let trendType = raw["latestGlucose_sensor_trendType"] as? Int { - glucoseTrend = GlucoseTrend(rawValue: trendType) - } - - sensor = SensorDisplayableContext( - isStateValid: state, - stateDescription: desc, - trendType: glucoseTrend, - isLocal: local) - } - - preferredUnitString = preferredString - latestGlucose = GlucoseContext( - quantity: latestValue, - startDate: startDate, - sensor: sensor) + + var rawValue: RawValue { + return [ + "startDate": startDate, + "unitVolume": unitVolume, + "capacity": capacity + ] + } + + init?(rawValue: RawValue) { + guard + let startDate = rawValue["startDate"] as? Date, + let unitVolume = rawValue["unitVolume"] as? Double, + let capacity = rawValue["capacity"] as? Int + else { + return nil } - - batteryPercentage = raw["batteryPercentage"] as? Double - - if let startDate = raw["reservoir_startDate"] as? Date, - let unitVolume = raw["reservoir_unitVolume"] as? Double, - let capacity = raw["reservoir_capacity"] as? Int { - reservoir = ReservoirContext(startDate: startDate, unitVolume: unitVolume, capacity: capacity) + + self.startDate = startDate + self.unitVolume = unitVolume + self.capacity = capacity + } +} + +extension LoopContext: RawRepresentable { + typealias RawValue = [String: Any] + + var rawValue: RawValue { + var raw: RawValue = [ + "dosingEnabled": dosingEnabled + ] + raw["lastCompleted"] = lastCompleted + return raw + } + + init?(rawValue: RawValue) { + guard let dosingEnabled = rawValue["dosingEnabled"] as? Bool + else { + return nil + } + + self.dosingEnabled = dosingEnabled + self.lastCompleted = rawValue["lastCompleted"] as? Date + } +} + +extension NetBasalContext: RawRepresentable { + typealias RawValue = [String: Any] + + var rawValue: RawValue { + return [ + "rate": rate, + "percentage": percentage, + "startDate": startDate + ] + } + + init?(rawValue: RawValue) { + guard + let rate = rawValue["rate"] as? Double, + let percentage = rawValue["percentage"] as? Double, + let startDate = rawValue["startDate"] as? Date + else { + return nil } - if let dosingEnabled = raw["loop_dosingEnabled"] as? Bool, - let lastCompleted = raw["loop_lastCompleted"] as? Date { - loop = LoopContext(dosingEnabled: dosingEnabled, lastCompleted: lastCompleted) + self.rate = rate + self.percentage = percentage + self.startDate = startDate + } +} + +extension SensorDisplayableContext: RawRepresentable { + typealias RawValue = [String: Any] + + var rawValue: RawValue { + var raw: RawValue = [ + "isStateValid": isStateValid, + "stateDescription": stateDescription, + "isLocal": isLocal + ] + raw["trendType"] = trendType?.rawValue + + return raw + } + + init(_ other: SensorDisplayable) { + isStateValid = other.isStateValid + stateDescription = other.stateDescription + isLocal = other.isLocal + trendType = other.trendType + } + + init?(rawValue: RawValue) { + guard + let isStateValid = rawValue["isStateValid"] as? Bool, + let stateDescription = rawValue["stateDescription"] as? String, + let isLocal = rawValue["isLocal"] as? Bool + else { + return nil } - - if let rate = raw["netBasal_rate"] as? Double, - let percentage = raw["netBasal_percentage"] as? Double, - let startDate = raw["netBasal_startDate"] as? Date { - netBasal = NetBasalContext(rate: rate, percentage: percentage, startDate: startDate) + + self.isStateValid = isStateValid + self.stateDescription = stateDescription + self.isLocal = isLocal + + if let rawValue = rawValue["trendType"] as? GlucoseTrend.RawValue { + trendType = GlucoseTrend(rawValue: rawValue) + } else { + trendType = nil } - - eventualGlucose = raw["eventualGlucose"] as? Double } - +} + +extension GlucoseContext: RawRepresentable { + typealias RawValue = [String: Any] + var rawValue: RawValue { var raw: RawValue = [ - "version": version + "value": value, + "unit": unit.unitString, + "startDate": startDate ] + raw["sensor"] = sensor?.rawValue - raw["preferredUnitString"] = preferredUnitString - - if preferredUnitString != nil, - let glucose = latestGlucose { - raw["latestGlucose_value"] = glucose.quantity - raw["latestGlucose_startDate"] = glucose.startDate + return raw + } + + init?(rawValue: RawValue) { + guard + let value = rawValue["value"] as? Double, + let unitString = rawValue["unit"] as? String, + let startDate = rawValue["startDate"] as? Date + else { + return nil } - if let sensor = latestGlucose?.sensor { - raw["latestGlucose_sensor_isStateValid"] = sensor.isStateValid - raw["latestGlucose_sensor_stateDescription"] = sensor.stateDescription - raw["latestGlucose_sensor_isLocal"] = sensor.isLocal - - if let trendType = sensor.trendType { - raw["latestGlucose_sensor_trendType"] = trendType.rawValue - } + self.value = value + self.unit = HKUnit(from: unitString) + self.startDate = startDate + + if let rawValue = rawValue["sensor"] as? SensorDisplayableContext.RawValue { + self.sensor = SensorDisplayableContext(rawValue: rawValue) + } else { + self.sensor = nil } + } +} - if let batteryPercentage = batteryPercentage { - raw["batteryPercentage"] = batteryPercentage +struct StatusExtensionContext: RawRepresentable { + typealias RawValue = [String: Any] + private let version = 2 + + var latestGlucose: GlucoseContext? + var reservoir: ReservoirContext? + var loop: LoopContext? + var netBasal: NetBasalContext? + var batteryPercentage: Double? + var eventualGlucose: GlucoseContext? + + init() { } + + init?(rawValue: RawValue) { + guard let version = rawValue["version"] as? Int, version == self.version else { + return nil + } + + if let rawValue = rawValue["latestGlucose"] as? GlucoseContext.RawValue { + latestGlucose = GlucoseContext(rawValue: rawValue) } - - if let reservoir = reservoir { - raw["reservoir_startDate"] = reservoir.startDate - raw["reservoir_unitVolume"] = reservoir.unitVolume - raw["reservoir_capacity"] = reservoir.capacity + + if let rawValue = rawValue["reservoir"] as? ReservoirContext.RawValue { + reservoir = ReservoirContext(rawValue: rawValue) } - - if let loop = loop { - raw["loop_dosingEnabled"] = loop.dosingEnabled - raw["loop_lastCompleted"] = loop.lastCompleted + + if let rawValue = rawValue["loop"] as? LoopContext.RawValue { + loop = LoopContext(rawValue: rawValue) } - - if let netBasal = netBasal { - raw["netBasal_rate"] = netBasal.rate - raw["netBasal_percentage"] = netBasal.percentage - raw["netBasal_startDate"] = netBasal.startDate + + if let rawValue = rawValue["netBasal"] as? NetBasalContext.RawValue { + netBasal = NetBasalContext(rawValue: rawValue) } - - if let eventualGlucose = eventualGlucose { - raw["eventualGlucose"] = eventualGlucose + + batteryPercentage = rawValue["batteryPercentage"] as? Double + + if let rawValue = rawValue["eventualGlucose"] as? GlucoseContext.RawValue { + eventualGlucose = GlucoseContext(rawValue: rawValue) } - + } + + var rawValue: RawValue { + var raw: RawValue = [ + "version": version + ] + raw["latestGlucose"] = latestGlucose?.rawValue + raw["reservoir"] = reservoir?.rawValue + raw["loop"] = loop?.rawValue + raw["netBasal"] = netBasal?.rawValue + raw["batteryPercentage"] = batteryPercentage + raw["eventualGlucose"] = eventualGlucose?.rawValue return raw } } diff --git a/Loop Status Extension/Base.lproj/MainInterface.storyboard b/Loop Status Extension/Base.lproj/MainInterface.storyboard index 05b95fa8dd..2386d61a45 100644 --- a/Loop Status Extension/Base.lproj/MainInterface.storyboard +++ b/Loop Status Extension/Base.lproj/MainInterface.storyboard @@ -1,11 +1,11 @@ - - + + - + @@ -29,20 +29,19 @@ - - - + + - - + + diff --git a/Loop Status Extension/StateColorPalette.swift b/Loop Status Extension/StateColorPalette.swift new file mode 100644 index 0000000000..c4e67ba9c4 --- /dev/null +++ b/Loop Status Extension/StateColorPalette.swift @@ -0,0 +1,16 @@ +// +// StateColorPalette.swift +// Loop +// +// Copyright © 2017 LoopKit Authors. All rights reserved. +// + +import LoopUI + +extension StateColorPalette { + static let loopStatus = StateColorPalette(unknown: .unknownColor, normal: .freshColor, warning: .agingColor, error: .staleColor) + + static let cgmStatus = loopStatus + + static let pumpStatus = StateColorPalette(unknown: .unknownColor, normal: .pumpStatusNormal, warning: .agingColor, error: .staleColor) +} diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index f7123e6c15..d2ecf5a65f 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -14,7 +14,16 @@ import UIKit class StatusViewController: UIViewController, NCWidgetProviding { - @IBOutlet weak var hudView: HUDView! + @IBOutlet weak var hudView: HUDView! { + didSet { + hudView.loopCompletionHUD.stateColors = .loopStatus + hudView.glucoseHUD.stateColors = .cgmStatus + hudView.glucoseHUD.tintColor = .glucoseTintColor + hudView.basalRateHUD.tintColor = .doseTintColor + hudView.reservoirVolumeHUD.stateColors = .pumpStatus + hudView.batteryHUD.stateColors = .pumpStatus + } + } @IBOutlet weak var subtitleLabel: UILabel! var statusExtensionContext: StatusExtensionContext? @@ -54,7 +63,7 @@ class StatusViewController: UIViewController, NCWidgetProviding { override func viewDidLoad() { super.viewDidLoad() subtitleLabel.alpha = 0 - subtitleLabel.textColor = UIColor.secondaryLabelColor + subtitleLabel.textColor = .subtitleLabelColor let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openLoopApp(_:))) view.addGestureRecognizer(tapGestureRecognizer) @@ -65,7 +74,8 @@ class StatusViewController: UIViewController, NCWidgetProviding { self, forKeyPath: defaults.statusExtensionContextObservableKey, options: [], - context: &observationContext) + context: &observationContext + ) } } @@ -84,10 +94,6 @@ class StatusViewController: UIViewController, NCWidgetProviding { update() } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - } - @objc private func openLoopApp(_: Any) { if let url = Bundle.main.mainAppUrl { self.extensionContext?.open(url) @@ -101,27 +107,16 @@ class StatusViewController: UIViewController, NCWidgetProviding { @discardableResult func update() -> NCUpdateResult { - guard - let context = defaults?.statusExtensionContext - else { - return NCUpdateResult.failed - } - - // We should never have the case where there's glucose values but no preferred - // unit. However, if that case were to happen we might show quantities against - // the wrong units and that could be very harmful. So unless there's a preferred - // unit, assume that none of the rest of the data is reliable. - guard - let preferredUnitString = context.preferredUnitString - else { + guard let context = defaults?.statusExtensionContext else { return NCUpdateResult.failed } if let glucose = context.latestGlucose { - glucoseHUD.set(glucoseQuantity: glucose.quantity, - at: glucose.startDate, - unitString: preferredUnitString, - from: glucose.sensor) + glucoseHUD.setGlucoseQuantity(glucose.value, + at: glucose.startDate, + unit: glucose.unit, + sensor: glucose.sensor + ) } if let batteryPercentage = context.batteryPercentage { @@ -142,19 +137,21 @@ class StatusViewController: UIViewController, NCWidgetProviding { loopCompletionHUD.lastLoopCompleted = loop.lastCompleted } - let preferredUnit = HKUnit(from: preferredUnitString) - let formatter = NumberFormatter.glucoseFormatter(for: preferredUnit) - if let eventualGlucose = context.eventualGlucose, - let eventualGlucoseNumberString = formatter.string(from: NSNumber(value: eventualGlucose)) { - subtitleLabel.text = String( + subtitleLabel.alpha = 0 + + if let eventualGlucose = context.eventualGlucose { + let formatter = NumberFormatter.glucoseFormatter(for: eventualGlucose.unit) + + if let eventualGlucoseNumberString = formatter.string(from: NSNumber(value: eventualGlucose.value)) { + subtitleLabel.text = String( format: NSLocalizedString( "Eventually %1$@ %2$@", comment: "The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description)"), eventualGlucoseNumberString, - preferredUnit.glucoseUnitDisplayString) - subtitleLabel.alpha = 1 - } else { - subtitleLabel.alpha = 0 + eventualGlucose.unit.glucoseUnitDisplayString + ) + subtitleLabel.alpha = 1 + } } // Right now we always act as if there's new data. diff --git a/Loop Status Extension/UIColor+Widget.swift b/Loop Status Extension/UIColor+Widget.swift new file mode 100644 index 0000000000..f0e9109222 --- /dev/null +++ b/Loop Status Extension/UIColor+Widget.swift @@ -0,0 +1,23 @@ +// +// UIColor+Widget.swift +// Loop +// +// Created by Nathan Racklyeft on 1/23/16. +// Copyright © 2016 LoopKit Authors. All rights reserved. +// + +import UIKit + +extension UIColor { + @nonobjc static let doseTintColor = UIColor(red: 255 / 255, green: 109 / 255, blue: 0, alpha: 1) + + @nonobjc static let glucoseTintColor = UIColor(red: 0 / 255, green: 122 / 255, blue: 244 / 255, alpha: 1) + + @nonobjc static let pumpStatusNormal = UIColor(red: 100 / 255, green: 101 / 255, blue: 105 / 255, alpha: 1) + + @nonobjc static let subtitleLabelColor = UIColor(white: 0, alpha: 0.4) +} + +extension UIColor { + @nonobjc static let freshColor = UIColor(red: 64 / 255, green: 219 / 255, blue: 89 / 255, alpha: 1) +} diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 02bc006664..5e4fe65a97 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 43027F0E1DFE0EC200C51989 /* NSNumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0E7A1D7DE13400D6475D /* NSNumberFormatter.swift */; }; 43027F0F1DFE0EC900C51989 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; 4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */; }; 4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */; }; @@ -85,6 +84,18 @@ 43A943901B926B7B0051FA24 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43A9438F1B926B7B0051FA24 /* Assets.xcassets */; }; 43A943941B926B7B0051FA24 /* WatchApp.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 43A943721B926B7B0051FA24 /* WatchApp.app */; }; 43B371881CE597D10013C5A6 /* ShareClient.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43B371871CE597D10013C5A6 /* ShareClient.framework */; }; + 43BFF0B21E45C18400FF19A9 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B11E45C18400FF19A9 /* UIColor.swift */; }; + 43BFF0B51E45C1E700FF19A9 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; + 43BFF0B71E45C20C00FF19A9 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; + 43BFF0BC1E45C80600FF19A9 /* UIColor+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0BB1E45C80600FF19A9 /* UIColor+Loop.swift */; }; + 43BFF0BF1E45C8EA00FF19A9 /* UIColor+Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0BE1E45C8EA00FF19A9 /* UIColor+Widget.swift */; }; + 43BFF0C21E464ACB00FF19A9 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B11E45C18400FF19A9 /* UIColor.swift */; }; + 43BFF0C51E465A2D00FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; + 43BFF0C61E465A4400FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; + 43BFF0C71E465A4F00FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; + 43BFF0C91E465B0A00FF19A9 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C81E465B0A00FF19A9 /* StateColorPalette.swift */; }; + 43BFF0CB1E466C0900FF19A9 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0CA1E466C0900FF19A9 /* StateColorPalette.swift */; }; + 43BFF0CD1E466C8400FF19A9 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0CC1E466C8400FF19A9 /* StateColorPalette.swift */; }; 43C0944A1CACCC73001F6403 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C094491CACCC73001F6403 /* NotificationManager.swift */; }; 43C246A81D89990F0031F8D1 /* Crypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246A71D89990F0031F8D1 /* Crypto.framework */; }; 43C418B51CE0575200405B6A /* ShareGlucose+GlucoseKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C418B41CE0575200405B6A /* ShareGlucose+GlucoseKit.swift */; }; @@ -98,6 +109,7 @@ 43DE92591C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */; }; 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */; }; 43DE92611C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */; }; + 43E0F0A51E46D1670064F7CE /* LevelHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E0F0A41E46D1670064F7CE /* LevelHUDView.swift */; }; 43E2D8C61D204678004DA55F /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2D8C51D204678004DA55F /* KeychainManager.swift */; }; 43E2D8C81D208D5B004DA55F /* KeychainManager+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2D8C71D208D5B004DA55F /* KeychainManager+Loop.swift */; }; 43E2D8D41D20BF42004DA55F /* DoseMathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2D8D31D20BF42004DA55F /* DoseMathTests.swift */; }; @@ -120,6 +132,9 @@ 43E3449F1B9D68E900C85C07 /* StatusTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E3449E1B9D68E900C85C07 /* StatusTableViewController.swift */; }; 43E344A41B9E1B1C00C85C07 /* NSUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E344A31B9E1B1C00C85C07 /* NSUserDefaults.swift */; }; 43E397A31D56B9E40028E321 /* Glucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E397A21D56B9E40028E321 /* Glucose.swift */; }; + 43E93FB51E4675E800EAB8DB /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; + 43E93FB61E469A4000EAB8DB /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; + 43E93FB71E469A5100EAB8DB /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; 43EB40861C82646A00472A8C /* StatusChartManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EB40851C82646A00472A8C /* StatusChartManager.swift */; }; 43F41C331D3A17AA00C11ED6 /* ChartAxisValueDoubleUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F41C321D3A17AA00C11ED6 /* ChartAxisValueDoubleUnit.swift */; }; 43F41C351D3B623800C11ED6 /* ChartPointsTouchHighlightLayerViewCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F41C341D3B623800C11ED6 /* ChartPointsTouchHighlightLayerViewCache.swift */; }; @@ -167,10 +182,8 @@ 4F7528A01DFE1F9D00C322D6 /* LoopStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438DADC71CDE8F8B007697A5 /* LoopStateView.swift */; }; 4F7528A11DFE200B00C322D6 /* BasalStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B371851CE583890013C5A6 /* BasalStateView.swift */; }; 4F7528A21DFE200B00C322D6 /* LevelMaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FBEDD71D73843700B21F22 /* LevelMaskView.swift */; }; - 4F7528A41DFE204900C322D6 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92501C541832001FFDE1 /* UIColor.swift */; }; 4F7528A51DFE208C00C322D6 /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; }; 4F7528A71DFE20CE00C322D6 /* SensorDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EA28611D517E42001BC233 /* SensorDisplayable.swift */; }; - 4F7528A81DFE20CE00C322D6 /* NSNumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0E7A1D7DE13400D6475D /* NSNumberFormatter.swift */; }; 4F7528A91DFE212600C322D6 /* GlucoseTrend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EA285E1D50ED3D001BC233 /* GlucoseTrend.swift */; }; 4F7528AA1DFE215100C322D6 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; 4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */; }; @@ -360,7 +373,6 @@ 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetBolusUserInfo.swift; sourceTree = ""; }; 43649A621C7A347F00523D7F /* CollectionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionType.swift; sourceTree = ""; }; 436A0DA41D236A2A00104B24 /* LoopError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopError.swift; sourceTree = ""; }; - 436A0E7A1D7DE13400D6475D /* NSNumberFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSNumberFormatter.swift; sourceTree = ""; }; 436FACED1D0BA636004E2427 /* InsulinDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinDataSource.swift; sourceTree = ""; }; 43776F8C1B8022E90074EA36 /* Loop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Loop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43776F8F1B8022E90074EA36 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -406,6 +418,14 @@ 43A943911B926B7B0051FA24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43B371851CE583890013C5A6 /* BasalStateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalStateView.swift; sourceTree = ""; }; 43B371871CE597D10013C5A6 /* ShareClient.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ShareClient.framework; path = Carthage/Build/iOS/ShareClient.framework; sourceTree = ""; }; + 43BFF0B11E45C18400FF19A9 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; + 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberFormatter.swift; sourceTree = ""; }; + 43BFF0BB1E45C80600FF19A9 /* UIColor+Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Loop.swift"; sourceTree = ""; }; + 43BFF0BE1E45C8EA00FF19A9 /* UIColor+Widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Widget.swift"; sourceTree = ""; }; + 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+HIG.swift"; sourceTree = ""; }; + 43BFF0C81E465B0A00FF19A9 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; + 43BFF0CA1E466C0900FF19A9 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; + 43BFF0CC1E466C8400FF19A9 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; 43C094491CACCC73001F6403 /* NotificationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 43C246A71D89990F0031F8D1 /* Crypto.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crypto.framework; path = Carthage/Build/iOS/Crypto.framework; sourceTree = ""; }; 43C418B41CE0575200405B6A /* ShareGlucose+GlucoseKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ShareGlucose+GlucoseKit.swift"; sourceTree = ""; }; @@ -417,9 +437,9 @@ 43DBF04B1C93B8D700B3C386 /* BolusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BolusViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 43DBF0521C93EC8200B3C386 /* DeviceDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DeviceDataManager.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 43DBF0581C93F73800B3C386 /* CarbEntryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarbEntryTableViewController.swift; sourceTree = ""; }; - 43DE92501C541832001FFDE1 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarbEntryUserInfo.swift; sourceTree = ""; }; 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AbsorptionTimeType+CarbKit.swift"; sourceTree = ""; }; + 43E0F0A41E46D1670064F7CE /* LevelHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LevelHUDView.swift; sourceTree = ""; }; 43E2D8C51D204678004DA55F /* KeychainManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = ""; }; 43E2D8C71D208D5B004DA55F /* KeychainManager+Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KeychainManager+Loop.swift"; sourceTree = ""; }; 43E2D8C91D20B9E7004DA55F /* KeychainManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManagerTests.swift; sourceTree = ""; }; @@ -757,16 +777,18 @@ 43E344A01B9E144300C85C07 /* Extensions */ = { isa = PBXGroup; children = ( - C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */, C17884621D51A7A400405663 /* BatteryIndicator.swift */, 4331E0771C85302200FBE832 /* CGPoint.swift */, 4346D1F51C78501000ABAFE3 /* ChartPoint.swift */, 43649A621C7A347F00523D7F /* CollectionType.swift */, - 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */, 43CE7CDD1CA8B63E003CC1B0 /* Data.swift */, + 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */, + C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */, 4398973A1CD2FC2000223065 /* NSDateFormatter.swift */, 43E344A31B9E1B1C00C85C07 /* NSUserDefaults.swift */, + 43BFF0CA1E466C0900FF19A9 /* StateColorPalette.swift */, 43F41C361D3BF32400C11ED6 /* UIAlertController.swift */, + 43BFF0BB1E45C80600FF19A9 /* UIColor+Loop.swift */, 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */, 434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */, C17824991E1999FA00D9D25C /* CaseCountable.swift */, @@ -847,18 +869,19 @@ isa = PBXGroup; children = ( 4F70C1FD1DE8E662006380B7 /* Loop Status Extension.entitlements */, + 4F70C1E51DE8DCA7006380B7 /* Info.plist */, + 43BFF0CC1E466C8400FF19A9 /* StateColorPalette.swift */, 4F70C1E01DE8DCA7006380B7 /* StatusViewController.swift */, + 43BFF0BE1E45C8EA00FF19A9 /* UIColor+Widget.swift */, 4F70C1E21DE8DCA7006380B7 /* MainInterface.storyboard */, - 4F70C1E51DE8DCA7006380B7 /* Info.plist */, ); path = "Loop Status Extension"; - sourceTree = SOURCE_ROOT; + sourceTree = ""; }; 4F75288C1DFE1DC600C322D6 /* LoopUI */ = { isa = PBXGroup; children = ( 4F7528A61DFE20AE00C322D6 /* Models */, - 4F7528A31DFE202B00C322D6 /* Extensions */, 4F7528931DFE1E1600C322D6 /* Views */, 4F75288D1DFE1DC600C322D6 /* LoopUI.h */, 4F75288E1DFE1DC600C322D6 /* Info.plist */, @@ -873,31 +896,24 @@ children = ( 437CEEBF1CD6FCD8003C8C80 /* BasalRateHUDView.swift */, 43B371851CE583890013C5A6 /* BasalStateView.swift */, + 437CEEBB1CD6DE6A003C8C80 /* BaseHUDView.swift */, 437CEEC91CD84DB7003C8C80 /* BatteryLevelHUDView.swift */, 4337615E1D52F487004A3647 /* GlucoseHUDView.swift */, - 437CEEBB1CD6DE6A003C8C80 /* BaseHUDView.swift */, + 4F2C15921E09BF2C00E160D4 /* HUDView.swift */, 43FBEDD71D73843700B21F22 /* LevelMaskView.swift */, 437CEEBD1CD6E0CB003C8C80 /* LoopCompletionHUDView.swift */, 438DADC71CDE8F8B007697A5 /* LoopStateView.swift */, 437CEEC71CD84CBB003C8C80 /* ReservoirVolumeHUDView.swift */, - 4F2C15921E09BF2C00E160D4 /* HUDView.swift */, + 43E0F0A41E46D1670064F7CE /* LevelHUDView.swift */, ); path = Views; sourceTree = ""; }; - 4F7528A31DFE202B00C322D6 /* Extensions */ = { - isa = PBXGroup; - children = ( - 43DE92501C541832001FFDE1 /* UIColor.swift */, - 436A0E7A1D7DE13400D6475D /* NSNumberFormatter.swift */, - ); - path = Extensions; - sourceTree = ""; - }; 4F7528A61DFE20AE00C322D6 /* Models */ = { isa = PBXGroup; children = ( 43EA28611D517E42001BC233 /* SensorDisplayable.swift */, + 43BFF0C81E465B0A00FF19A9 /* StateColorPalette.swift */, ); path = Models; sourceTree = ""; @@ -933,6 +949,9 @@ 430DA58D1D4AEC230097D1CA /* NSBundle.swift */, 439897341CD2F7DE00223065 /* NSTimeInterval.swift */, 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */, + 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */, + 43BFF0B11E45C18400FF19A9 /* UIColor.swift */, + 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */, ); path = Extensions; sourceTree = ""; @@ -1338,6 +1357,7 @@ 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */, 437CCADA1D284ADF0075D2C3 /* AuthenticationTableViewCell.swift in Sources */, 43CE7CDE1CA8B63E003CC1B0 /* Data.swift in Sources */, + 43BFF0CB1E466C0900FF19A9 /* StateColorPalette.swift in Sources */, 43F41C331D3A17AA00C11ED6 /* ChartAxisValueDoubleUnit.swift in Sources */, 43F5C2DB1B92A5E1003EB13D /* SettingsTableViewController.swift in Sources */, C11C87DD1E21E53500BB71D3 /* GlucoseThreshold.swift in Sources */, @@ -1361,8 +1381,10 @@ 437CEEE41CDE5C0A003C8C80 /* UIImage.swift in Sources */, 43DBF0591C93F73800B3C386 /* CarbEntryTableViewController.swift in Sources */, 43F41C351D3B623800C11ED6 /* ChartPointsTouchHighlightLayerViewCache.swift in Sources */, + 43E93FB71E469A5100EAB8DB /* HKUnit.swift in Sources */, 43EB40861C82646A00472A8C /* StatusChartManager.swift in Sources */, C17884631D51A7A400405663 /* BatteryIndicator.swift in Sources */, + 43BFF0BC1E45C80600FF19A9 /* UIColor+Loop.swift in Sources */, 43C0944A1CACCC73001F6403 /* NotificationManager.swift in Sources */, 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */, C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */, @@ -1374,6 +1396,7 @@ C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */, 43DBF04C1C93B8D700B3C386 /* BolusViewController.swift in Sources */, 4328E0351CFC0AE100E199AA /* WatchDataManager.swift in Sources */, + 43BFF0C51E465A2D00FF19A9 /* UIColor+HIG.swift in Sources */, 4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */, 437CCAE01D285C7B0075D2C3 /* ServiceAuthentication.swift in Sources */, 4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */, @@ -1381,6 +1404,7 @@ 430DA5901D4B0E4C0097D1CA /* MySentryPumpStatusMessageBody.swift in Sources */, 4D5B7A4B1D457CCA00796CA9 /* GlucoseG4.swift in Sources */, 438849EC1D29EC34003B3F23 /* AmplitudeService.swift in Sources */, + 43E93FB61E469A4000EAB8DB /* NumberFormatter.swift in Sources */, 435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */, 437D9BA31D7BC977007245E8 /* PredictionTableViewController.swift in Sources */, 43F41C371D3BF32400C11ED6 /* UIAlertController.swift in Sources */, @@ -1393,6 +1417,7 @@ 436A0DA51D236A2A00104B24 /* LoopError.swift in Sources */, 43E2D8C61D204678004DA55F /* KeychainManager.swift in Sources */, 433EA4C21D9F39C900CD78FB /* PumpIDTableViewController.swift in Sources */, + 43BFF0B21E45C18400FF19A9 /* UIColor.swift in Sources */, 43F78D261C8FC000002152D1 /* DoseMath.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, 4331E07A1C85650D00FBE832 /* ChartAxisValueDoubleLog.swift in Sources */, @@ -1432,10 +1457,10 @@ 4328E01E1CFBE25F00E199AA /* AddCarbsInterfaceController.swift in Sources */, 43846ADB1D91057000799272 /* ContextUpdatable.swift in Sources */, 4328E0261CFBE2C500E199AA /* IdentifiableClass.swift in Sources */, - 43027F0E1DFE0EC200C51989 /* NSNumberFormatter.swift in Sources */, 43027F0F1DFE0EC900C51989 /* HKUnit.swift in Sources */, 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */, 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */, + 43BFF0B51E45C1E700FF19A9 /* NumberFormatter.swift in Sources */, 43A9438E1B926B7B0051FA24 /* ComplicationController.swift in Sources */, 4328E01A1CFBE1DA00E199AA /* StatusInterfaceController.swift in Sources */, 435400351C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */, @@ -1469,8 +1494,13 @@ buildActionMask = 2147483647; files = ( 4F2C15831E0757E600E160D4 /* HKUnit.swift in Sources */, + 43E93FB51E4675E800EAB8DB /* NumberFormatter.swift in Sources */, + 43BFF0CD1E466C8400FF19A9 /* StateColorPalette.swift in Sources */, + 43BFF0C21E464ACB00FF19A9 /* UIColor.swift in Sources */, 4F526D621DF9D95200A04910 /* NSBundle.swift in Sources */, 4FC8C8021DEB943800A1452E /* NSUserDefaults+StatusExtension.swift in Sources */, + 43BFF0C71E465A4F00FF19A9 /* UIColor+HIG.swift in Sources */, + 43BFF0BF1E45C8EA00FF19A9 /* UIColor+Widget.swift in Sources */, 4F70C2121DE900EA006380B7 /* StatusExtensionContext.swift in Sources */, 4F70C1E11DE8DCA7006380B7 /* StatusViewController.swift in Sources */, ); @@ -1485,17 +1515,19 @@ 4F7528AA1DFE215100C322D6 /* HKUnit.swift in Sources */, 4F7528A91DFE212600C322D6 /* GlucoseTrend.swift in Sources */, 4F7528A71DFE20CE00C322D6 /* SensorDisplayable.swift in Sources */, - 4F7528A81DFE20CE00C322D6 /* NSNumberFormatter.swift in Sources */, 4F2C15931E09BF2C00E160D4 /* HUDView.swift in Sources */, + 43BFF0B71E45C20C00FF19A9 /* NumberFormatter.swift in Sources */, 4F7528A51DFE208C00C322D6 /* NSTimeInterval.swift in Sources */, - 4F7528A41DFE204900C322D6 /* UIColor.swift in Sources */, 4F7528A11DFE200B00C322D6 /* BasalStateView.swift in Sources */, 4F7528A21DFE200B00C322D6 /* LevelMaskView.swift in Sources */, + 43BFF0C61E465A4400FF19A9 /* UIColor+HIG.swift in Sources */, 4F7528A01DFE1F9D00C322D6 /* LoopStateView.swift in Sources */, 4F75289A1DFE1F6000C322D6 /* BasalRateHUDView.swift in Sources */, 4F75289B1DFE1F6000C322D6 /* BatteryLevelHUDView.swift in Sources */, 4F75289C1DFE1F6000C322D6 /* GlucoseHUDView.swift in Sources */, 4F75289D1DFE1F6000C322D6 /* BaseHUDView.swift in Sources */, + 43BFF0C91E465B0A00FF19A9 /* StateColorPalette.swift in Sources */, + 43E0F0A51E46D1670064F7CE /* LevelHUDView.swift in Sources */, 4F75289E1DFE1F6000C322D6 /* LoopCompletionHUDView.swift in Sources */, 4F75289F1DFE1F6000C322D6 /* ReservoirVolumeHUDView.swift in Sources */, ); @@ -1705,7 +1737,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", @@ -1726,7 +1758,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", @@ -1745,7 +1777,7 @@ ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).watchkitapp.watchkitextension"; @@ -1763,7 +1795,7 @@ ASSETCATALOG_COMPILER_COMPLICATION_NAME = Complication; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).watchkitapp.watchkitextension"; @@ -1782,7 +1814,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1802,7 +1834,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1888,7 +1920,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", @@ -1912,7 +1944,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 57NRR26737; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Carthage/Build/iOS", @@ -1937,13 +1969,14 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.loudnate.Loop.LoopUI; + PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).LoopUI"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -1964,13 +1997,14 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.loudnate.Loop.LoopUI; + PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).LoopUI"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; diff --git a/Loop/Extensions/StateColorPalette.swift b/Loop/Extensions/StateColorPalette.swift new file mode 100644 index 0000000000..c4e67ba9c4 --- /dev/null +++ b/Loop/Extensions/StateColorPalette.swift @@ -0,0 +1,16 @@ +// +// StateColorPalette.swift +// Loop +// +// Copyright © 2017 LoopKit Authors. All rights reserved. +// + +import LoopUI + +extension StateColorPalette { + static let loopStatus = StateColorPalette(unknown: .unknownColor, normal: .freshColor, warning: .agingColor, error: .staleColor) + + static let cgmStatus = loopStatus + + static let pumpStatus = StateColorPalette(unknown: .unknownColor, normal: .pumpStatusNormal, warning: .agingColor, error: .staleColor) +} diff --git a/Loop/Extensions/UIColor+Loop.swift b/Loop/Extensions/UIColor+Loop.swift new file mode 100644 index 0000000000..864654fbf0 --- /dev/null +++ b/Loop/Extensions/UIColor+Loop.swift @@ -0,0 +1,20 @@ +// +// UIColor+Loop.swift +// Loop +// +// Created by Nathan Racklyeft on 1/23/16. +// Copyright © 2016 LoopKit Authors. All rights reserved. +// + + +import UIKit + +extension UIColor { + @nonobjc static let doseTintColor = UIColor.HIGOrangeColor() + + @nonobjc static let freshColor = UIColor.HIGGreenColor() + + @nonobjc static let glucoseTintColor = UIColor(red: 0 / 255, green: 176 / 255, blue: 255 / 255, alpha: 1) + + @nonobjc static let pumpStatusNormal = secondaryLabelColor +} diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index e2ba0bca07..bf2f03d276 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -86,7 +86,7 @@ final class DeviceDataManager: CarbStoreDelegate, CarbStoreSyncDelegate, DoseSto } else if let status = latestPumpStatus { return batteryChemistry.chargeRemaining(voltage: status.batteryVolts) } else { - return nil + return statusExtensionManager.context?.batteryPercentage } } } @@ -106,7 +106,6 @@ final class DeviceDataManager: CarbStoreDelegate, CarbStoreSyncDelegate, DoseSto } } - // MARK: - RileyLink @objc private func receivedRileyLinkManagerNotification(_ note: Notification) { @@ -1126,9 +1125,12 @@ final class DeviceDataManager: CarbStoreDelegate, CarbStoreSyncDelegate, DoseSto NotificationCenter.default.addObserver(self, selector: #selector(pumpStateValuesDidChange(_:)), name: .PumpStateValuesDidChange, object: pumpState) } - loopManager = LoopDataManager(deviceDataManager: self) - watchManager = WatchDataManager(deviceDataManager: self) statusExtensionManager = StatusExtensionDataManager(deviceDataManager: self) + loopManager = LoopDataManager( + deviceDataManager: self, + lastLoopCompleted: statusExtensionManager.context?.loop?.lastCompleted + ) + watchManager = WatchDataManager(deviceDataManager: self) nightscoutDataManager = NightscoutDataManager(deviceDataManager: self) carbStore?.delegate = self diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 3d300c09a0..52fed83e18 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -48,8 +48,9 @@ final class LoopDataManager { } } - init(deviceDataManager: DeviceDataManager) { + init(deviceDataManager: DeviceDataManager, lastLoopCompleted: Date?) { self.deviceDataManager = deviceDataManager + self.lastLoopCompleted = lastLoopCompleted dosingEnabled = UserDefaults.standard.dosingEnabled retrospectiveCorrectionEnabled = UserDefaults.standard.retrospectiveCorrectionEnabled diff --git a/Loop/Managers/StatusExtensionDataManager.swift b/Loop/Managers/StatusExtensionDataManager.swift index daa72eede0..d56775374b 100644 --- a/Loop/Managers/StatusExtensionDataManager.swift +++ b/Loop/Managers/StatusExtensionDataManager.swift @@ -11,6 +11,7 @@ import UIKit import CarbKit import LoopKit + final class StatusExtensionDataManager { unowned let dataManager: DeviceDataManager @@ -24,11 +25,14 @@ final class StatusExtensionDataManager { return UserDefaults(suiteName: Bundle.main.appGroupSuiteName) } - @objc private func update(_ notification: Notification) { + var context: StatusExtensionContext? { + return defaults?.statusExtensionContext + } + @objc private func update(_ notification: Notification) { self.dataManager.glucoseStore?.preferredUnit() { (unit, error) in if error == nil, let unit = unit { - self.createContext(unit) { (context) in + self.createContext(glucoseUnit: unit) { (context) in if let context = context { self.defaults?.statusExtensionContext = context } @@ -37,7 +41,7 @@ final class StatusExtensionDataManager { } } - private func createContext(_ preferredUnit: HKUnit, _ completionHandler: @escaping (_ context: StatusExtensionContext?) -> Void) { + private func createContext(glucoseUnit: HKUnit, _ completionHandler: @escaping (_ context: StatusExtensionContext?) -> Void) { guard let glucoseStore = self.dataManager.glucoseStore else { completionHandler(nil) return @@ -45,38 +49,49 @@ final class StatusExtensionDataManager { dataManager.loopManager.getLoopStatus { (predictedGlucose, _, recommendedTempBasal, lastTempBasal, lastLoopCompleted, _, _, error) in - - if error != nil { - // TODO: unclear how to handle the error here properly. - completionHandler(nil) - } - + let dataManager = self.dataManager - let context = StatusExtensionContext() + var context = StatusExtensionContext() #if IOS_SIMULATOR // If we're in the simulator, there's a higher likelihood that we don't have // a fully configured app. Inject some baseline debug data to let us test the // experience. This data will be overwritten by actual data below, if available. context.batteryPercentage = 0.25 - context.reservoir = ReservoirContext(startDate: Date(), unitVolume: 42.5, capacity: 200) - context.netBasal = NetBasalContext(rate: 2.1, percentage: 0.6, startDate: Date() - TimeInterval(250)) - context.eventualGlucose = HKQuantity(unit: HKUnit.milligramsPerDeciliterUnit(), doubleValue: 89.123) - .doubleValue(for: preferredUnit) + context.reservoir = ReservoirContext(startDate: Date(), unitVolume: 160, capacity: 300) + context.netBasal = NetBasalContext( + rate: 2.1, + percentage: 0.6, + startDate: + Date(timeIntervalSinceNow: -250) + ) + context.eventualGlucose = GlucoseContext( + value: 89.123, + unit: HKUnit.milligramsPerDeciliterUnit(), + startDate: Date(timeIntervalSinceNow: TimeInterval(hours: 4)), + sensor: nil + ) + + let lastLoopCompleted = Date(timeIntervalSinceNow: -TimeInterval(minutes: 0)) + #else + guard error == nil else { + // TODO: unclear how to handle the error here properly. + completionHandler(nil) + return + } #endif - context.preferredUnitString = preferredUnit.unitString context.loop = LoopContext( dosingEnabled: dataManager.loopManager.dosingEnabled, lastCompleted: lastLoopCompleted) if let glucose = glucoseStore.latestGlucose { - // It's possible that the unit came in nil and we defaulted to mg/dL. To account for that case, - // convert the latest glucose to those units just to be sure. context.latestGlucose = GlucoseContext( - quantity: glucose.quantity.doubleValue(for: preferredUnit), + value: glucose.quantity.doubleValue(for: glucoseUnit), + unit: glucoseUnit, startDate: glucose.startDate, - sensor: dataManager.sensorInfo) + sensor: dataManager.sensorInfo != nil ? SensorDisplayableContext(dataManager.sensorInfo!) : nil + ) } if let lastNetBasal = dataManager.loopManager.lastNetBasal { @@ -88,7 +103,8 @@ final class StatusExtensionDataManager { context.reservoir = ReservoirContext( startDate: reservoir.startDate, unitVolume: reservoir.unitVolume, - capacity: capacity) + capacity: capacity + ) } if let batteryPercentage = dataManager.pumpBatteryChargeRemaining { @@ -96,7 +112,12 @@ final class StatusExtensionDataManager { } if let lastPoint = predictedGlucose?.last { - context.eventualGlucose = lastPoint.quantity.doubleValue(for: preferredUnit) + context.eventualGlucose = GlucoseContext( + value: lastPoint.quantity.doubleValue(for: glucoseUnit), + unit: glucoseUnit, + startDate: lastPoint.startDate, + sensor: nil + ) } completionHandler(context) @@ -110,7 +131,8 @@ extension StatusExtensionDataManager: CustomDebugStringConvertible { return [ "## StatusExtensionDataManager", "appGroupName: \(Bundle.main.appGroupSuiteName)", - "statusExtensionContext: \(String(reflecting: defaults?.statusExtensionContext))" + "statusExtensionContext: \(String(reflecting: defaults?.statusExtensionContext))", + "" ].joined(separator: "\n") } } diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 09de18b661..503a43d4e6 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -29,13 +29,13 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize notificationCenter.addObserver(forName: .LoopDataUpdated, object: dataManager.loopManager, queue: nil) { _ in DispatchQueue.main.async { self.needsRefresh = true - self.loopCompletionHUD.loopInProgress = false + self.hudView.loopCompletionHUD.loopInProgress = false self.reloadData(animated: true) } }, notificationCenter.addObserver(forName: .LoopRunning, object: dataManager.loopManager, queue: nil) { _ in DispatchQueue.main.async { - self.loopCompletionHUD.loopInProgress = true + self.hudView.loopCompletionHUD.loopInProgress = true } }, notificationCenter.addObserver(forName: .LoopSettingsUpdated, object: dataManager, queue: nil) { _ in @@ -114,7 +114,7 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize private var active = true { didSet { reloadData() - loopCompletionHUD.assertTimer(active) + hudView.loopCompletionHUD.assertTimer(active) } } @@ -250,17 +250,17 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize if let reservoir = dataManager.doseStore.lastReservoirValue { if let capacity = dataManager.pumpState?.pumpModel?.reservoirCapacity { - reservoirVolumeHUD.reservoirLevel = min(1, max(0, Double(reservoir.unitVolume / Double(capacity)))) + hudView.reservoirVolumeHUD.reservoirLevel = min(1, max(0, Double(reservoir.unitVolume / Double(capacity)))) } - reservoirVolumeHUD.setReservoirVolume(volume: reservoir.unitVolume, at: reservoir.startDate) + hudView.reservoirVolumeHUD.setReservoirVolume(volume: reservoir.unitVolume, at: reservoir.startDate) } if let level = dataManager.pumpBatteryChargeRemaining { - batteryLevelHUD.batteryLevel = level + hudView.batteryHUD.batteryLevel = level } - loopCompletionHUD.dosingEnabled = dataManager.loopManager.dosingEnabled + hudView.loopCompletionHUD.dosingEnabled = dataManager.loopManager.dosingEnabled charts.glucoseTargetRangeSchedule = dataManager.glucoseTargetRangeSchedule @@ -268,10 +268,11 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize reloadGroup.notify(queue: DispatchQueue.main) { if let glucose = self.dataManager.glucoseStore?.latestGlucose { - self.glucoseHUD.set(glucoseQuantity: glucose.quantity.doubleValue(for: self.charts.glucoseUnit), - at: glucose.startDate, - unitString: self.charts.glucoseUnit.unitString, - from: self.dataManager.sensorInfo) + self.hudView.glucoseHUD.setGlucoseQuantity(glucose.quantity.doubleValue(for: self.charts.glucoseUnit), + at: glucose.startDate, + unit: self.charts.glucoseUnit, + sensor: self.dataManager.sensorInfo + ) } self.charts.prerender() @@ -393,7 +394,7 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize didSet { if let lastNetBasal = self.dataManager.loopManager.lastNetBasal { DispatchQueue.main.async { - self.basalRateHUD.setNetBasalRate(lastNetBasal.rate, percent: lastNetBasal.percent, at: lastNetBasal.startDate) + self.hudView.basalRateHUD.setNetBasalRate(lastNetBasal.rate, percent: lastNetBasal.percent, at: lastNetBasal.startDate) } } } @@ -402,7 +403,7 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize private var lastLoopCompleted: Date? { didSet { DispatchQueue.main.async { - self.loopCompletionHUD.lastLoopCompleted = self.lastLoopCompleted + self.hudView.loopCompletionHUD.lastLoopCompleted = self.lastLoopCompleted } } } @@ -770,27 +771,23 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize @IBOutlet weak var hudView: HUDView! { didSet { + let statusTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showLastError(_:))) + hudView.loopCompletionHUD.addGestureRecognizer(statusTapGestureRecognizer) + hudView.loopCompletionHUD.accessibilityHint = NSLocalizedString("Shows last loop error", comment: "Loop Completion HUD accessibility hint") + let glucoseTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openCGMApp(_:))) - glucoseHUD.addGestureRecognizer(glucoseTapGestureRecognizer) + hudView.glucoseHUD.addGestureRecognizer(glucoseTapGestureRecognizer) if cgmAppURL != nil { - glucoseHUD.accessibilityHint = NSLocalizedString("Launches CGM app", comment: "Glucose HUD accessibility hint") + hudView.glucoseHUD.accessibilityHint = NSLocalizedString("Launches CGM app", comment: "Glucose HUD accessibility hint") } - let statusTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showLastError(_:))) - loopCompletionHUD.addGestureRecognizer(statusTapGestureRecognizer) - loopCompletionHUD.accessibilityHint = NSLocalizedString("Show last loop error", comment: "Loop Completion HUD accessibility hint") - } - } - - var loopCompletionHUD: LoopCompletionHUDView! { - get { - return hudView.loopCompletionHUD - } - } - - var glucoseHUD: GlucoseHUDView! { - get { - return hudView.glucoseHUD + + hudView.loopCompletionHUD.stateColors = .loopStatus + hudView.glucoseHUD.stateColors = .cgmStatus + hudView.glucoseHUD.tintColor = .glucoseTintColor + hudView.basalRateHUD.tintColor = .doseTintColor + hudView.reservoirVolumeHUD.stateColors = .pumpStatus + hudView.batteryHUD.stateColors = .pumpStatus } } @@ -817,22 +814,4 @@ final class StatusTableViewController: UITableViewController, UIGestureRecognize UIApplication.shared.open(url) } } - - var basalRateHUD: BasalRateHUDView! { - get { - return hudView.basalRateHUD - } - } - - var reservoirVolumeHUD: ReservoirVolumeHUDView! { - get { - return hudView.reservoirVolumeHUD - } - } - - var batteryLevelHUD: BatteryLevelHUDView! { - get { - return hudView.batteryHUD - } - } } diff --git a/LoopUI/Extensions/UIColor.swift b/LoopUI/Extensions/UIColor.swift deleted file mode 100644 index 414ffa7361..0000000000 --- a/LoopUI/Extensions/UIColor.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// UIColor.swift -// Naterade -// -// Created by Nathan Racklyeft on 1/23/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import UIKit - - -extension UIColor { - @nonobjc public static var tintColor: UIColor? = nil - - @nonobjc public static let secondaryLabelColor = UIColor.HIGGrayColor() - - @nonobjc public static let cellBackgroundColor = UIColor(white: 239 / 255, alpha: 1) - - @nonobjc public static let gridColor = UIColor(white: 193 / 255, alpha: 1) - - @nonobjc public static let glucoseTintColor = UIColor(red: 0 / 255, green: 176 / 255, blue: 255 / 255, alpha: 1) - - @nonobjc public static let IOBTintColor = UIColor.HIGOrangeColor() - - @nonobjc public static let COBTintColor = UIColor(red: 99 / 255, green: 218 / 255, blue: 56 / 255, alpha: 1) - - @nonobjc public static let doseTintColor = UIColor.HIGOrangeColor() - - @nonobjc public static let freshColor = UIColor.HIGGreenColor() - - @nonobjc public static let agingColor = UIColor.HIGYellowColor() - - @nonobjc public static let staleColor = UIColor.HIGRedColor() - - @nonobjc public static let unknownColor = UIColor(red: 198 / 255, green: 199 / 255, blue: 201 / 255, alpha: 1) - - @nonobjc public static let deleteColor = UIColor.HIGRedColor() - - // MARK: - HIG colors - // See: https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/ - - private static func HIGTealBlueColor() -> UIColor { - return UIColor(red: 90 / 255, green: 200 / 255, blue: 250 / 255, alpha: 1) - } - - private static func HIGYellowColor() -> UIColor { - return UIColor(red: 1, green: 204 / 255, blue: 0, alpha: 1) - } - - private static func HIGOrangeColor() -> UIColor { - return UIColor(red: 1, green: 149 / 255, blue: 0 / 255, alpha: 1) - } - - private static func HIGPinkColor() -> UIColor { - return UIColor(red: 1, green: 45 / 255, blue: 85 / 255, alpha: 1) - } - - private static func HIGBlueColor() -> UIColor { - return UIColor(red: 0, green: 122 / 255, blue: 1, alpha: 1) - } - - private static func HIGGreenColor() -> UIColor { - return UIColor(red: 76 / 255, green: 217 / 255, blue: 100 / 255, alpha: 1) - } - - private static func HIGRedColor() -> UIColor { - return UIColor(red: 1, green: 59 / 255, blue: 48 / 255, alpha: 1) - } - - private static func HIGPurpleColor() -> UIColor { - return UIColor(red: 88 / 255, green: 86 / 255, blue: 214 / 255, alpha: 1) - } - - private static func HIGGrayColor() -> UIColor { - return UIColor(red: 142 / 255, green: 143 / 255, blue: 147 / 255, alpha: 1) - } - -} diff --git a/LoopUI/HUDView.xib b/LoopUI/HUDView.xib index 22535f225a..f30f330b35 100644 --- a/LoopUI/HUDView.xib +++ b/LoopUI/HUDView.xib @@ -1,11 +1,11 @@ - + - + @@ -32,9 +32,8 @@ - + - @@ -47,7 +46,6 @@ - @@ -97,7 +95,6 @@ - @@ -139,13 +136,11 @@ - - @@ -178,17 +173,16 @@ - - - + + + - @@ -208,7 +202,6 @@ - @@ -240,17 +233,16 @@ - - - + + + - @@ -263,7 +255,6 @@ - diff --git a/LoopUI/Models/StateColorPalette.swift b/LoopUI/Models/StateColorPalette.swift new file mode 100644 index 0000000000..20052f10b2 --- /dev/null +++ b/LoopUI/Models/StateColorPalette.swift @@ -0,0 +1,24 @@ +// +// StateColorPalette.swift +// Loop +// +// Copyright © 2017 LoopKit Authors. All rights reserved. +// + +import UIKit + + +/// A collection of colors for displaying state +public struct StateColorPalette { + public let unknown: UIColor + public let normal: UIColor + public let warning: UIColor + public let error: UIColor + + public init(unknown: UIColor, normal: UIColor, warning: UIColor, error: UIColor) { + self.unknown = unknown + self.normal = normal + self.warning = warning + self.error = error + } +} diff --git a/LoopUI/Views/BasalRateHUDView.swift b/LoopUI/Views/BasalRateHUDView.swift index cd253c3ab6..87df03bbcf 100644 --- a/LoopUI/Views/BasalRateHUDView.swift +++ b/LoopUI/Views/BasalRateHUDView.swift @@ -16,12 +16,21 @@ public final class BasalRateHUDView: BaseHUDView { @IBOutlet private weak var basalRateLabel: UILabel! { didSet { basalRateLabel?.text = String(format: basalRateFormatString, "–") - basalRateLabel?.textColor = .doseTintColor + updateTintColor() accessibilityValue = NSLocalizedString("Unknown", comment: "Accessibility value for an unknown value") } } + public override func tintColorDidChange() { + super.tintColorDidChange() + updateTintColor() + } + + private func updateTintColor() { + basalRateLabel?.textColor = tintColor + } + private lazy var basalRateFormatString = NSLocalizedString("%@ U", comment: "The format string describing the basal rate.") public func setNetBasalRate(_ rate: Double, percent: Double, at date: Date) { diff --git a/LoopUI/Views/BasalStateView.swift b/LoopUI/Views/BasalStateView.swift index 59332eb91a..411eb518dd 100644 --- a/LoopUI/Views/BasalStateView.swift +++ b/LoopUI/Views/BasalStateView.swift @@ -29,22 +29,30 @@ public final class BasalStateView: UIView { super.init(frame: frame) shapeLayer.lineWidth = 2 - shapeLayer.fillColor = UIColor.doseTintColor.withAlphaComponent(0.5).cgColor - shapeLayer.strokeColor = UIColor.doseTintColor.cgColor + updateTintColor() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) shapeLayer.lineWidth = 2 - shapeLayer.fillColor = UIColor.doseTintColor.withAlphaComponent(0.5).cgColor - shapeLayer.strokeColor = UIColor.doseTintColor.cgColor + updateTintColor() } override public func layoutSubviews() { super.layoutSubviews() } + public override func tintColorDidChange() { + super.tintColorDidChange() + updateTintColor() + } + + private func updateTintColor() { + shapeLayer.fillColor = tintColor.withAlphaComponent(0.5).cgColor + shapeLayer.strokeColor = tintColor.cgColor + } + private func drawPath() -> CGPath { let startX = bounds.minX let endX = bounds.maxX diff --git a/LoopUI/Views/BatteryLevelHUDView.swift b/LoopUI/Views/BatteryLevelHUDView.swift index 8fcd1688d3..d3dfba28ef 100644 --- a/LoopUI/Views/BatteryLevelHUDView.swift +++ b/LoopUI/Views/BatteryLevelHUDView.swift @@ -9,17 +9,7 @@ import UIKit -public final class BatteryLevelHUDView: BaseHUDView { - - @IBOutlet private weak var levelMaskView: LevelMaskView! - - override public func awakeFromNib() { - super.awakeFromNib() - - tintColor = .unknownColor - - accessibilityValue = NSLocalizedString("Unknown", comment: "Accessibility value for an unknown value") - } +public final class BatteryLevelHUDView: LevelHUDView { private lazy var numberFormatter: NumberFormatter = { let formatter = NumberFormatter() @@ -28,7 +18,6 @@ public final class BatteryLevelHUDView: BaseHUDView { return formatter }() - public var batteryLevel: Double? { didSet { if let value = batteryLevel, let level = numberFormatter.string(from: NSNumber(value: value)) { @@ -38,18 +27,7 @@ public final class BatteryLevelHUDView: BaseHUDView { caption.text = nil } - switch batteryLevel { - case .none: - tintColor = .unknownColor - case let x? where x > 0.25: - tintColor = .secondaryLabelColor - case let x? where x > 0.10: - tintColor = .agingColor - default: - tintColor = .staleColor - } - - levelMaskView.value = batteryLevel ?? 1.0 + level = batteryLevel } } diff --git a/LoopUI/Views/GlucoseHUDView.swift b/LoopUI/Views/GlucoseHUDView.swift index fe6cf7d830..59b3b8ee3a 100644 --- a/LoopUI/Views/GlucoseHUDView.swift +++ b/LoopUI/Views/GlucoseHUDView.swift @@ -15,27 +15,50 @@ public final class GlucoseHUDView: BaseHUDView { @IBOutlet private weak var unitLabel: UILabel! { didSet { unitLabel.text = "–" - unitLabel.textColor = .glucoseTintColor + unitLabel.textColor = tintColor } } @IBOutlet private weak var glucoseLabel: UILabel! { didSet { glucoseLabel.text = "–" - glucoseLabel.textColor = .glucoseTintColor + glucoseLabel.textColor = tintColor } } @IBOutlet private weak var alertLabel: UILabel! { didSet { alertLabel.alpha = 0 - alertLabel.backgroundColor = UIColor.agingColor alertLabel.textColor = UIColor.white alertLabel.layer.cornerRadius = 9 alertLabel.clipsToBounds = true } } + public override func tintColorDidChange() { + super.tintColorDidChange() + + unitLabel.textColor = tintColor + glucoseLabel.textColor = tintColor + } + + public var stateColors: StateColorPalette? { + didSet { + updateColor() + } + } + + private func updateColor() { + switch sensorAlertState { + case .missing, .invalid: + alertLabel.backgroundColor = stateColors?.warning + case .remote: + alertLabel.backgroundColor = stateColors?.unknown + case .ok: + alertLabel.backgroundColor = stateColors?.normal + } + } + private enum SensorAlertState { case ok case missing @@ -51,25 +74,24 @@ public final class GlucoseHUDView: BaseHUDView { case .ok: alertLabelAlpha = 0 case .missing, .invalid: - alertLabel.backgroundColor = UIColor.agingColor alertLabel.text = "!" case .remote: - alertLabel.backgroundColor = UIColor.unknownColor alertLabel.text = "☁︎" } + updateColor() + UIView.animate(withDuration: 0.25, animations: { self.alertLabel.alpha = alertLabelAlpha }) } } - public func set(glucoseQuantity: Double, at glucoseStartDate: Date, unitString: String, from sensor: SensorDisplayable?) { + public func setGlucoseQuantity(_ glucoseQuantity: Double, at glucoseStartDate: Date, unit: HKUnit, sensor: SensorDisplayable?) { var accessibilityStrings = [String]() let time = timeFormatter.string(from: glucoseStartDate) caption?.text = time - let unit = HKUnit(from: unitString) let numberFormatter = NumberFormatter.glucoseFormatter(for: unit) if let valueString = numberFormatter.string(from: NSNumber(value: glucoseQuantity)) { diff --git a/LoopUI/Views/LevelHUDView.swift b/LoopUI/Views/LevelHUDView.swift new file mode 100644 index 0000000000..f15be707f0 --- /dev/null +++ b/LoopUI/Views/LevelHUDView.swift @@ -0,0 +1,52 @@ +// +// LevelHUDView.swift +// Loop +// +// Created by Nate Racklyeft on 2/4/17. +// Copyright © 2017 LoopKit Authors. All rights reserved. +// + +import UIKit + +public class LevelHUDView: BaseHUDView { + + @IBOutlet private weak var levelMaskView: LevelMaskView! + + override public func awakeFromNib() { + super.awakeFromNib() + + updateColor() + + accessibilityValue = NSLocalizedString("Unknown", comment: "Accessibility value for an unknown value") + } + + public var stateColors: StateColorPalette? { + didSet { + updateColor() + } + } + + private func updateColor() { + levelMaskView.tintColor = nil + + switch level { + case .none: + tintColor = stateColors?.unknown + case let x? where x > 0.25: + tintColor = stateColors?.normal + case let x? where x > 0.10: + tintColor = stateColors?.normal + levelMaskView.tintColor = stateColors?.warning + default: + tintColor = stateColors?.error + } + } + + internal var level: Double? { + didSet { + levelMaskView.value = level ?? 1.0 + updateColor() + } + } + +} diff --git a/LoopUI/Views/LoopCompletionHUDView.swift b/LoopUI/Views/LoopCompletionHUDView.swift index 1d3da43a1a..03fce31072 100644 --- a/LoopUI/Views/LoopCompletionHUDView.swift +++ b/LoopUI/Views/LoopCompletionHUDView.swift @@ -12,6 +12,19 @@ public final class LoopCompletionHUDView: BaseHUDView { @IBOutlet private weak var loopStateView: LoopStateView! + enum Freshness { + case fresh + case aging + case stale + case unknown + } + + private(set) var freshness = Freshness.unknown { + didSet { + updateTintColor() + } + } + override public func awakeFromNib() { super.awakeFromNib() @@ -46,6 +59,29 @@ public final class LoopCompletionHUDView: BaseHUDView { } } + public var stateColors: StateColorPalette? { + didSet { + updateTintColor() + } + } + + private func updateTintColor() { + let tintColor: UIColor? + + switch freshness { + case .fresh: + tintColor = stateColors?.normal + case .aging: + tintColor = stateColors?.warning + case .stale: + tintColor = stateColors?.error + case .unknown: + tintColor = stateColors?.unknown + } + + self.tintColor = tintColor + } + private func initTimer(_ startDate: Date) { let updateInterval = TimeInterval(minutes: 1) @@ -73,7 +109,7 @@ public final class LoopCompletionHUDView: BaseHUDView { private lazy var formatter: DateComponentsFormatter = { let formatter = DateComponentsFormatter() - formatter.allowedUnits = [.hour, .minute] + formatter.allowedUnits = [.day, .hour, .minute] formatter.maximumUnitCount = 1 formatter.unitsStyle = .short @@ -86,11 +122,13 @@ public final class LoopCompletionHUDView: BaseHUDView { switch ago { case let t where t.minutes <= 5: - loopStateView.freshness = .fresh + freshness = .fresh case let t where t.minutes <= 15: - loopStateView.freshness = .aging + freshness = .aging + case let t where t.hours <= 12: + freshness = .stale default: - loopStateView.freshness = .stale + freshness = .unknown } if let timeString = formatter.string(from: ago) { diff --git a/LoopUI/Views/LoopStateView.swift b/LoopUI/Views/LoopStateView.swift index 96c1ffbd35..b1375c5733 100644 --- a/LoopUI/Views/LoopStateView.swift +++ b/LoopUI/Views/LoopStateView.swift @@ -8,33 +8,17 @@ import UIKit -public final class LoopStateView: UIView { +final class LoopStateView: UIView { var firstDataUpdate = true - enum Freshness { - case fresh - case aging - case stale - case unknown - - var color: UIColor { - switch self { - case .fresh: - return UIColor.freshColor - case .aging: - return UIColor.agingColor - case .stale: - return UIColor.staleColor - case .unknown: - return UIColor.unknownColor - } - } + override func tintColorDidChange() { + super.tintColorDidChange() + + updateTintColor() } - var freshness = Freshness.unknown { - didSet { - shapeLayer.strokeColor = freshness.color.cgColor - } + private func updateTintColor() { + shapeLayer.strokeColor = tintColor.cgColor } var open = false { @@ -45,7 +29,7 @@ public final class LoopStateView: UIView { } } - override public class var layerClass : AnyClass { + override class var layerClass : AnyClass { return CAShapeLayer.self } @@ -58,22 +42,22 @@ public final class LoopStateView: UIView { shapeLayer.lineWidth = 8 shapeLayer.fillColor = UIColor.clear.cgColor - shapeLayer.strokeColor = freshness.color.cgColor + updateTintColor() shapeLayer.path = drawPath() } - required public init?(coder aDecoder: NSCoder) { + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) shapeLayer.lineWidth = 8 shapeLayer.fillColor = UIColor.clear.cgColor - shapeLayer.strokeColor = freshness.color.cgColor + updateTintColor() shapeLayer.path = drawPath() } - override public func layoutSubviews() { + override func layoutSubviews() { super.layoutSubviews() shapeLayer.path = drawPath() diff --git a/LoopUI/Views/ReservoirVolumeHUDView.swift b/LoopUI/Views/ReservoirVolumeHUDView.swift index e3eb5df7fb..9039c5fc20 100644 --- a/LoopUI/Views/ReservoirVolumeHUDView.swift +++ b/LoopUI/Views/ReservoirVolumeHUDView.swift @@ -8,38 +8,29 @@ import UIKit -public final class ReservoirVolumeHUDView: BaseHUDView { - - @IBOutlet private weak var levelMaskView: LevelMaskView! +public final class ReservoirVolumeHUDView: LevelHUDView { @IBOutlet private weak var volumeLabel: UILabel! override public func awakeFromNib() { super.awakeFromNib() - tintColor = .unknownColor volumeLabel.isHidden = true - - accessibilityValue = NSLocalizedString("Unknown", comment: "Accessibility value for an unknown value") } public var reservoirLevel: Double? { didSet { - levelMaskView.value = reservoirLevel ?? 1.0 + level = reservoirLevel switch reservoirLevel { case .none: - tintColor = .unknownColor volumeLabel.isHidden = true case let x? where x > 0.25: - tintColor = .secondaryLabelColor volumeLabel.isHidden = true case let x? where x > 0.10: - tintColor = .agingColor volumeLabel.textColor = tintColor volumeLabel.isHidden = false default: - tintColor = .staleColor volumeLabel.textColor = tintColor volumeLabel.isHidden = false }