Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DoseMathTests/DoseMathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class RecommendTempBasalTests: XCTestCase {
}

var glucoseTargetRange: GlucoseRangeSchedule {
return GlucoseRangeSchedule(unit: HKUnit.milligramsPerDeciliter(), dailyItems: [RepeatingScheduleValue(startTime: TimeInterval(0), value: DoubleRange(minValue: 90, maxValue: 120))], workoutRange: nil)!
return GlucoseRangeSchedule(unit: HKUnit.milligramsPerDeciliter(), dailyItems: [RepeatingScheduleValue(startTime: TimeInterval(0), value: DoubleRange(minValue: 90, maxValue: 120))], overrideRanges: [:])!
}

var insulinSensitivitySchedule: InsulinSensitivitySchedule {
Expand Down Expand Up @@ -486,7 +486,7 @@ class RecommendBolusTests: XCTestCase {
}

var glucoseTargetRange: GlucoseRangeSchedule {
return GlucoseRangeSchedule(unit: HKUnit.milligramsPerDeciliter(), dailyItems: [RepeatingScheduleValue(startTime: TimeInterval(0), value: DoubleRange(minValue: 90, maxValue: 120))], workoutRange: nil)!
return GlucoseRangeSchedule(unit: HKUnit.milligramsPerDeciliter(), dailyItems: [RepeatingScheduleValue(startTime: TimeInterval(0), value: DoubleRange(minValue: 90, maxValue: 120))], overrideRanges: [:])!
}

var insulinSensitivitySchedule: InsulinSensitivitySchedule {
Expand Down
12 changes: 12 additions & 0 deletions Loop/Assets.xcassets/Pre-Meal Selected.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Pre-Meal Selected.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.
12 changes: 12 additions & 0 deletions Loop/Assets.xcassets/Pre-Meal.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Pre-Meal.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.
2 changes: 1 addition & 1 deletion Loop/Assets.xcassets/carbs.imageset/Contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"images" : [
{
"idiom" : "universal",
"filename" : "carbs.pdf"
"filename" : "Meal.pdf"
}
],
"info" : {
Expand Down
Binary file added Loop/Assets.xcassets/carbs.imageset/Meal.pdf
Binary file not shown.
Binary file removed Loop/Assets.xcassets/carbs.imageset/carbs.pdf
Binary file not shown.
17 changes: 8 additions & 9 deletions Loop/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -697,18 +697,16 @@
<segue destination="YJ1-01-QoA" kind="presentation" identifier="CarbEntryEditViewController" id="viB-eQ-p7y"/>
</connections>
</barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="g6k-L0-2qh"/>
<barButtonItem enabled="NO" image="Pre-Meal" id="jdk-2o-ydO"/>
<barButtonItem style="plain" systemItem="flexibleSpace" id="ava-EZ-v6i"/>
<barButtonItem image="bolus" id="2kb-DB-Vag">
<connections>
<segue destination="m6L-HW-ENd" kind="presentation" identifier="BolusViewController" id="PC7-vP-CUY"/>
</connections>
</barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="g6k-L0-2qh"/>
<barButtonItem enabled="NO" image="workout" id="8Qz-Cj-oVB">
<connections>
<action selector="toggleWorkoutMode:" destination="1zR-tC-i50" id="coy-UU-6Zu"/>
</connections>
</barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="p68-fH-iuk"/>
<barButtonItem enabled="NO" image="workout" id="8Qz-Cj-oVB"/>
<barButtonItem style="plain" systemItem="flexibleSpace" id="d5h-bj-8ek"/>
<barButtonItem image="settings" id="PHJ-4n-qiF">
<connections>
Expand Down Expand Up @@ -1020,15 +1018,16 @@
</scene>
</scenes>
<resources>
<image name="Pre-Meal" width="29" height="29"/>
<image name="Uploading" width="32" height="20"/>
<image name="bolus" width="22" height="24"/>
<image name="carbs" width="22" height="23"/>
<image name="carbs" width="29" height="29"/>
<image name="settings" width="25" height="40"/>
<image name="workout" width="30" height="29"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="PC7-vP-CUY"/>
<segue reference="viB-eQ-p7y"/>
<segue reference="xCA-IG-jXB"/>
<segue reference="UnO-ql-F0K"/>
<segue reference="Isc-NM-GBt"/>
</inferredMetricsTieBreakers>
</document>
8 changes: 4 additions & 4 deletions Loop/Extensions/ChartPoint+Loop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ extension ChartPoint {
return ChartPoint.pointsForDatedRanges(targetRanges, xAxisValues: xAxisValues)
}

static func pointsForGlucoseRangeScheduleOverrideDuration(_ override: AbsoluteScheduleValue<DoubleRange>, xAxisValues: [ChartAxisValue]) -> [ChartPoint] {
static func pointsForGlucoseRangeScheduleOverrideDuration(_ override: GlucoseRangeSchedule.Override, xAxisValues: [ChartAxisValue]) -> [ChartPoint] {
return ChartPoint.pointsForDatedRangeOverrideDuration(
DatedRangeContext(startDate: override.startDate, endDate: override.endDate, minValue: override.value.minValue, maxValue: override.value.maxValue),
DatedRangeContext(startDate: override.start, endDate: override.end ?? .distantFuture, minValue: override.value.minValue, maxValue: override.value.maxValue),
xAxisValues: xAxisValues)
}

static func pointsForGlucoseRangeScheduleOverride(_ override: AbsoluteScheduleValue<DoubleRange>, xAxisValues: [ChartAxisValue]) -> [ChartPoint] {
static func pointsForGlucoseRangeScheduleOverride(_ override: GlucoseRangeSchedule.Override, xAxisValues: [ChartAxisValue]) -> [ChartPoint] {
return ChartPoint.pointsForDatedRangeOverride(
DatedRangeContext(startDate: override.startDate, endDate: override.endDate, minValue: override.value.minValue, maxValue: override.value.maxValue),
DatedRangeContext(startDate: override.start, endDate: override.end ?? .distantFuture, minValue: override.value.minValue, maxValue: override.value.maxValue),
xAxisValues: xAxisValues)
}
}
Expand Down
11 changes: 8 additions & 3 deletions Loop/Extensions/GlucoseRangeSchedule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ import LoopKit


extension GlucoseRangeSchedule {
var workoutModeEnabled: Bool? {
guard let override = temporaryOverride else {
func overrideEnabledForContext(_ context: Override.Context) -> Bool? {
guard let override = override, override.context == context else {
guard let value = overrideRanges[context], !value.isZero else {
// Unavailable to set
return nil
}

return false
}

return override.endDate.timeIntervalSinceNow > 0
return override.isActive()
}
}
4 changes: 4 additions & 0 deletions Loop/Extensions/UIImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ extension UIImage {
return UIImage(named: "reservoir_\(imageSuffixForLevel(level))")
}

static func preMealImage(selected: Bool) -> UIImage? {
return UIImage(named: selected ? "Pre-Meal Selected" : "Pre-Meal")
}

static func workoutImage(selected: Bool) -> UIImage? {
return UIImage(named: selected ? "workout-selected" : "workout")
}
Expand Down
2 changes: 1 addition & 1 deletion Loop/Managers/GlucoseRangeScheduleCalculator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class GlucoseRangeScheduleCalculator: TargetPointsCalculator {
{
glucosePoints = ChartPoint.pointsForGlucoseRangeSchedule(schedule, xAxisValues: xAxisValues)

if let override = schedule.temporaryOverride {
if let override = schedule.override {
overridePoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(override, xAxisValues: xAxisValues)

overrideDurationPoints = ChartPoint.pointsForGlucoseRangeScheduleOverrideDuration(override, xAxisValues: xAxisValues)
Expand Down
49 changes: 13 additions & 36 deletions Loop/Managers/LoopDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class LoopDataManager {

let doseStore: DoseStore

let glucoseStore: GlucoseStore! = GlucoseStore()
let glucoseStore: GlucoseStore!

unowned let delegate: LoopDataManagerDelegate

Expand All @@ -57,7 +57,10 @@ final class LoopDataManager {
self.lastTempBasal = lastTempBasal
self.settings = settings

let healthStore = HKHealthStore()

carbStore = CarbStore(
healthStore: healthStore,
defaultAbsorptionTimes: (
fast: TimeInterval(hours: 2),
medium: TimeInterval(hours: 3),
Expand All @@ -73,6 +76,8 @@ final class LoopDataManager {
insulinSensitivitySchedule: insulinSensitivitySchedule
)

glucoseStore = GlucoseStore(healthStore: healthStore)

// Observe changes
carbUpdateObserver = NotificationCenter.default.addObserver(
forName: .CarbEntriesDidUpdate,
Expand Down Expand Up @@ -130,24 +135,6 @@ final class LoopDataManager {
}
}

/// Enable workout glucose targets until the given date
///
/// TODO: When schedule settings are migrated to structs, this can be simplified
///
/// - Parameter endDate: The date the workout targets should end
/// - Returns: True if the override was set
@discardableResult
func enableWorkoutMode(until endDate: Date) -> Bool {
if settings.glucoseTargetRangeSchedule != nil {
_ = settings.glucoseTargetRangeSchedule!.setWorkoutOverride(until: endDate)
}


notify(forChange: .preferences)

return true
}

/// Disable any active workout glucose targets
func disableWorkoutMode() {
settings.glucoseTargetRangeSchedule?.clearOverride()
Expand Down Expand Up @@ -212,23 +199,10 @@ final class LoopDataManager {
///
/// - Parameter timeZone: The time zone
func setScheduleTimeZone(_ timeZone: TimeZone) {
// Recreate each schedule to force a change notification
// TODO: When schedule settings are migrated to structs, this can be simplified
if let basalRateSchedule = basalRateSchedule {
self.basalRateSchedule = BasalRateSchedule(dailyItems: basalRateSchedule.items, timeZone: timeZone)
}

if let carbRatioSchedule = carbRatioSchedule {
self.carbRatioSchedule = CarbRatioSchedule(unit: carbRatioSchedule.unit, dailyItems: carbRatioSchedule.items, timeZone: timeZone)
}

if let insulinSensitivitySchedule = insulinSensitivitySchedule {
self.insulinSensitivitySchedule = InsulinSensitivitySchedule(unit: insulinSensitivitySchedule.unit, dailyItems: insulinSensitivitySchedule.items, timeZone: timeZone)
}

if let glucoseTargetRangeSchedule = settings.glucoseTargetRangeSchedule {
settings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: glucoseTargetRangeSchedule.unit, dailyItems: glucoseTargetRangeSchedule.items, workoutRange: glucoseTargetRangeSchedule.workoutRange, timeZone: timeZone)
}
self.basalRateSchedule?.timeZone = timeZone
self.carbRatioSchedule?.timeZone = timeZone
self.insulinSensitivitySchedule?.timeZone = timeZone
settings.glucoseTargetRangeSchedule?.timeZone = timeZone
}

// MARK: - Intake
Expand Down Expand Up @@ -273,6 +247,9 @@ final class LoopDataManager {
let addCompletion: (Bool, CarbEntry?, CarbStore.CarbStoreError?) -> Void = { (success, _, error) in
self.dataAccessQueue.async {
if success {
// Remove the active pre-meal target override
self.settings.glucoseTargetRangeSchedule?.clearOverride(matching: .preMeal)

self.carbEffect = nil
self.carbsOnBoard = nil

Expand Down
9 changes: 5 additions & 4 deletions Loop/Managers/StatusExtensionDataManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,13 @@ final class StatusExtensionDataManager {
)
}

if let override = targetRanges.temporaryOverride {
if let override = targetRanges.override {
context.temporaryOverride = DatedRangeContext(
startDate: override.startDate,
endDate: override.endDate,
startDate: override.start,
endDate: override.end ?? .distantFuture,
minValue: override.value.minValue,
maxValue: override.value.maxValue)
maxValue: override.value.maxValue
)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Loop/View Controllers/SettingsTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu
scheduleVC.timeZone = schedule.timeZone
scheduleVC.scheduleItems = schedule.items
scheduleVC.unit = schedule.unit
scheduleVC.workoutRange = schedule.workoutRange
scheduleVC.overrideRanges = schedule.overrideRanges

show(scheduleVC, sender: sender)
} else {
Expand Down Expand Up @@ -802,7 +802,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu
}
case .glucoseTargetRange:
if let controller = controller as? GlucoseRangeScheduleTableViewController {
dataManager.loopManager.settings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: controller.unit, dailyItems: controller.scheduleItems, workoutRange: controller.workoutRange, timeZone: controller.timeZone)
dataManager.loopManager.settings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone, overrideRanges: controller.overrideRanges, override: dataManager.loopManager.settings.glucoseTargetRangeSchedule?.override)
AnalyticsManager.shared.didChangeGlucoseTargetRangeSchedule()
}
case let row:
Expand Down
61 changes: 50 additions & 11 deletions Loop/View Controllers/StatusTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ final class StatusTableViewController: ChartsTableViewController {
// Toolbar
toolbarItems![0].accessibilityLabel = NSLocalizedString("Add Meal", comment: "The label of the carb entry button")
toolbarItems![0].tintColor = UIColor.COBTintColor
toolbarItems![2].accessibilityLabel = NSLocalizedString("Bolus", comment: "The label of the bolus entry button")
toolbarItems![2].tintColor = UIColor.doseTintColor
toolbarItems![6].accessibilityLabel = NSLocalizedString("Settings", comment: "The label of the settings button")
toolbarItems![6].tintColor = UIColor.secondaryLabelColor
toolbarItems![4].accessibilityLabel = NSLocalizedString("Bolus", comment: "The label of the bolus entry button")
toolbarItems![4].tintColor = UIColor.doseTintColor
toolbarItems![8].accessibilityLabel = NSLocalizedString("Settings", comment: "The label of the settings button")
toolbarItems![8].tintColor = UIColor.secondaryLabelColor
}

override func didReceiveMemoryWarning() {
Expand Down Expand Up @@ -297,7 +297,8 @@ final class StatusTableViewController: ChartsTableViewController {

hudView.loopCompletionHUD.dosingEnabled = deviceManager.loopManager.settings.dosingEnabled

workoutMode = deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.workoutModeEnabled
workoutMode = deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.overrideEnabledForContext(.workout)
preMealMode = deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.overrideEnabledForContext(.preMeal)

reloadGroup.notify(queue: .main) {
if let glucose = self.deviceManager.loopManager.glucoseStore.latestGlucose {
Expand Down Expand Up @@ -432,16 +433,30 @@ final class StatusTableViewController: ChartsTableViewController {

// MARK: - Toolbar data

private var preMealMode: Bool? = nil {
didSet {
guard oldValue != preMealMode else {
return
}

if let preMealMode = preMealMode {
toolbarItems![2] = createPreMealButtonItem(selected: preMealMode)
} else {
toolbarItems![2].isEnabled = false
}
}
}

private var workoutMode: Bool? = nil {
didSet {
guard oldValue != workoutMode else {
return
}

if let workoutMode = workoutMode {
toolbarItems![4] = createWorkoutButtonItem(selected: workoutMode)
toolbarItems![6] = createWorkoutButtonItem(selected: workoutMode)
} else {
toolbarItems![4].isEnabled = false
toolbarItems![6].isEnabled = false
}
}
}
Expand Down Expand Up @@ -722,9 +737,25 @@ final class StatusTableViewController: ChartsTableViewController {
@IBAction func unwindFromSettings(_ segue: UIStoryboardSegue) {
}

private func createPreMealButtonItem(selected: Bool) -> UIBarButtonItem {
let item = UIBarButtonItem(image: UIImage.preMealImage(selected: selected), style: .plain, target: self, action: #selector(togglePreMealMode(_:)))
item.accessibilityLabel = NSLocalizedString("Pre-Meal Targets", comment: "The label of the pre-meal mode toggle button")

if selected {
item.accessibilityTraits = item.accessibilityTraits | UIAccessibilityTraitSelected
item.accessibilityHint = NSLocalizedString("Disables", comment: "The action hint of the workout mode toggle button when enabled")
} else {
item.accessibilityHint = NSLocalizedString("Enables", comment: "The action hint of the workout mode toggle button when disabled")
}

item.tintColor = UIColor.COBTintColor

return item
}

private func createWorkoutButtonItem(selected: Bool) -> UIBarButtonItem {
let item = UIBarButtonItem(image: UIImage.workoutImage(selected: selected), style: .plain, target: self, action: #selector(toggleWorkoutMode(_:)))
item.accessibilityLabel = NSLocalizedString("Workout Mode", comment: "The label of the workout mode toggle button")
item.accessibilityLabel = NSLocalizedString("Workout Targets", comment: "The label of the workout mode toggle button")

if selected {
item.accessibilityTraits = item.accessibilityTraits | UIAccessibilityTraitSelected
Expand All @@ -738,12 +769,20 @@ final class StatusTableViewController: ChartsTableViewController {
return item
}

@IBAction func togglePreMealMode(_ sender: UIBarButtonItem) {
if preMealMode == true {
deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.clearOverride(matching: .preMeal)
} else {
_ = self.deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.setOverride(.preMeal, until: Date(timeIntervalSinceNow: .hours(1)))
}
}

@IBAction func toggleWorkoutMode(_ sender: UIBarButtonItem) {
if let workoutModeEnabled = workoutMode, workoutModeEnabled {
deviceManager.loopManager.disableWorkoutMode()
if workoutMode == true {
deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.clearOverride(matching: .workout)
} else {
let vc = UIAlertController(workoutDurationSelectionHandler: { (endDate) in
self.deviceManager.loopManager.enableWorkoutMode(until: endDate)
_ = self.deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.setOverride(.workout, until: endDate)
})

present(vc, animated: true, completion: nil)
Expand Down