Skip to content

Commit 07a00a0

Browse files
authored
[LOOP-3193] watch uses display unit (#373)
* watch uses display unit and sync with settings unit updates * fixing unit tests * using closed HKQuantity range in loop settings * cleaned up unit test
1 parent 36af4ad commit 07a00a0

File tree

12 files changed

+56
-41
lines changed

12 files changed

+56
-41
lines changed

Common/Models/WatchContext.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class WatchContext: RawRepresentable {
1818

1919
var creationDate = Date()
2020

21-
var preferredGlucoseUnit: HKUnit?
21+
var displayGlucoseUnit: HKUnit?
2222

2323
var glucose: HKQuantity?
2424
var glucoseTrendRawValue: Int?
@@ -61,9 +61,9 @@ final class WatchContext: RawRepresentable {
6161
isClosedLoop = rawValue["cl"] as? Bool
6262

6363
if let unitString = rawValue["gu"] as? String {
64-
preferredGlucoseUnit = HKUnit(from: unitString)
64+
displayGlucoseUnit = HKUnit(from: unitString)
6565
}
66-
let unit = preferredGlucoseUnit ?? .milligramsPerDeciliter
66+
let unit = displayGlucoseUnit ?? .milligramsPerDeciliter
6767
if let glucoseValue = rawValue["gv"] as? Double {
6868
glucose = HKQuantity(unit: unit, doubleValue: glucoseValue)
6969
}
@@ -110,8 +110,8 @@ final class WatchContext: RawRepresentable {
110110

111111
raw["cob"] = cob
112112

113-
let unit = preferredGlucoseUnit ?? .milligramsPerDeciliter
114-
raw["gu"] = preferredGlucoseUnit?.unitString
113+
let unit = displayGlucoseUnit ?? .milligramsPerDeciliter
114+
raw["gu"] = displayGlucoseUnit?.unitString
115115
raw["gv"] = glucose?.doubleValue(for: unit)
116116

117117
raw["gt"] = glucoseTrendRawValue

Loop/Extensions/SettingsStore+SimulatedCoreData.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ fileprivate extension StoredSettings {
113113
return StoredSettings(date: date,
114114
dosingEnabled: true,
115115
glucoseTargetRangeSchedule: glucoseTargetRangeSchedule,
116-
preMealTargetRange: DoubleRange(minValue: 80.0, maxValue: 90.0),
117-
workoutTargetRange: DoubleRange(minValue: 150.0, maxValue: 160.0),
116+
preMealTargetRange: DoubleRange(minValue: 80.0, maxValue: 90.0).quantityRange(for: .milligramsPerDeciliter),
117+
workoutTargetRange: DoubleRange(minValue: 150.0, maxValue: 160.0).quantityRange(for: .milligramsPerDeciliter),
118118
overridePresets: nil,
119119
scheduleOverride: nil,
120120
preMealOverride: preMealOverride,

Loop/Models/WatchContext+LoopKit.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ extension WatchContext {
1818
self.glucoseDate = glucose?.startDate
1919
self.glucoseIsDisplayOnly = glucose?.isDisplayOnly
2020
self.glucoseWasUserEntered = glucose?.wasUserEntered
21-
self.preferredGlucoseUnit = glucoseUnit
21+
self.displayGlucoseUnit = glucoseUnit
2222
}
2323
}

LoopCore/LoopSettings.swift

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public struct LoopSettings: Equatable {
2020

2121
public var glucoseTargetRangeSchedule: GlucoseRangeSchedule?
2222

23-
public var preMealTargetRange: DoubleRange?
23+
public var preMealTargetRange: ClosedRange<HKQuantity>?
2424

25-
public var legacyWorkoutTargetRange: DoubleRange?
25+
public var legacyWorkoutTargetRange: ClosedRange<HKQuantity>?
2626

2727
public var indefiniteWorkoutOverrideEnabledDate: Date?
2828

@@ -154,12 +154,12 @@ extension LoopSettings {
154154
}
155155

156156
private func makePreMealOverride(beginningAt date: Date = Date(), for duration: TimeInterval) -> TemporaryScheduleOverride? {
157-
guard let preMealTargetRange = preMealTargetRange, let unit = glucoseUnit else {
157+
guard let preMealTargetRange = preMealTargetRange else {
158158
return nil
159159
}
160160
return TemporaryScheduleOverride(
161161
context: .preMeal,
162-
settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: preMealTargetRange),
162+
settings: TemporaryScheduleOverrideSettings(targetRange: preMealTargetRange),
163163
startDate: date,
164164
duration: .finite(duration),
165165
enactTrigger: .local,
@@ -173,7 +173,7 @@ extension LoopSettings {
173173
}
174174

175175
public mutating func legacyWorkoutOverride(beginningAt date: Date = Date(), for duration: TimeInterval) -> TemporaryScheduleOverride? {
176-
guard let legacyWorkoutTargetRange = legacyWorkoutTargetRange, let unit = glucoseUnit else {
176+
guard let legacyWorkoutTargetRange = legacyWorkoutTargetRange else {
177177
return nil
178178
}
179179

@@ -183,7 +183,7 @@ extension LoopSettings {
183183

184184
return TemporaryScheduleOverride(
185185
context: .legacyWorkout,
186-
settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: legacyWorkoutTargetRange),
186+
settings: TemporaryScheduleOverrideSettings(targetRange: legacyWorkoutTargetRange),
187187
startDate: date,
188188
duration: duration.isInfinite ? .indefinite : .finite(duration),
189189
enactTrigger: .local,
@@ -216,6 +216,7 @@ extension LoopSettings {
216216
extension LoopSettings: RawRepresentable {
217217
public typealias RawValue = [String: Any]
218218
private static let version = 1
219+
fileprivate static let codingGlucoseUnit = HKUnit.milligramsPerDeciliter
219220

220221
public init?(rawValue: RawValue) {
221222
guard
@@ -235,20 +236,20 @@ extension LoopSettings: RawRepresentable {
235236
// Migrate the glucose range schedule override targets
236237
if let overrideRangesRawValue = glucoseRangeScheduleRawValue["overrideRanges"] as? [String: DoubleRange.RawValue] {
237238
if let preMealTargetRawValue = overrideRangesRawValue["preMeal"] {
238-
self.preMealTargetRange = DoubleRange(rawValue: preMealTargetRawValue)
239+
self.preMealTargetRange = DoubleRange(rawValue: preMealTargetRawValue)?.quantityRange(for: LoopSettings.codingGlucoseUnit)
239240
}
240241
if let legacyWorkoutTargetRawValue = overrideRangesRawValue["workout"] {
241-
self.legacyWorkoutTargetRange = DoubleRange(rawValue: legacyWorkoutTargetRawValue)
242+
self.legacyWorkoutTargetRange = DoubleRange(rawValue: legacyWorkoutTargetRawValue)?.quantityRange(for: LoopSettings.codingGlucoseUnit)
242243
}
243244
}
244245
}
245246

246247
if let rawPreMealTargetRange = rawValue["preMealTargetRange"] as? DoubleRange.RawValue {
247-
self.preMealTargetRange = DoubleRange(rawValue: rawPreMealTargetRange)
248+
self.preMealTargetRange = DoubleRange(rawValue: rawPreMealTargetRange)?.quantityRange(for: LoopSettings.codingGlucoseUnit)
248249
}
249250

250251
if let rawLegacyWorkoutTargetRange = rawValue["legacyWorkoutTargetRange"] as? DoubleRange.RawValue {
251-
self.legacyWorkoutTargetRange = DoubleRange(rawValue: rawLegacyWorkoutTargetRange)
252+
self.legacyWorkoutTargetRange = DoubleRange(rawValue: rawLegacyWorkoutTargetRange)?.quantityRange(for: LoopSettings.codingGlucoseUnit)
252253
}
253254

254255
self.indefiniteWorkoutOverrideEnabledDate = rawValue["indefiniteWorkoutOverrideEnabledDate"] as? Date
@@ -282,8 +283,8 @@ extension LoopSettings: RawRepresentable {
282283
]
283284

284285
raw["glucoseTargetRangeSchedule"] = glucoseTargetRangeSchedule?.rawValue
285-
raw["preMealTargetRange"] = preMealTargetRange?.rawValue
286-
raw["legacyWorkoutTargetRange"] = legacyWorkoutTargetRange?.rawValue
286+
raw["preMealTargetRange"] = preMealTargetRange?.doubleRange(for: LoopSettings.codingGlucoseUnit).rawValue
287+
raw["legacyWorkoutTargetRange"] = legacyWorkoutTargetRange?.doubleRange(for: LoopSettings.codingGlucoseUnit).rawValue
287288
raw["indefiniteWorkoutOverrideEnabledDate"] = indefiniteWorkoutOverrideEnabledDate
288289
raw["preMealOverride"] = preMealOverride?.rawValue
289290
raw["scheduleOverride"] = scheduleOverride?.rawValue

LoopTests/LoopSettingsAlerterTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ class LoopSettingsAlerterTests: XCTestCase, LoopSettingsAlerterDelegate {
2121
var settings: LoopSettings = LoopSettings()
2222

2323
override func setUp() {
24-
settings.preMealTargetRange = DoubleRange(minValue: 80, maxValue: 80)
24+
settings.preMealTargetRange = DoubleRange(minValue: 80, maxValue: 80).quantityRange(for: .milligramsPerDeciliter)
2525
settings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(
2626
unit: .milligramsPerDeciliter,
2727
dailyItems: [.init(startTime: 0, value: DoubleRange(minValue: 95, maxValue: 105))]
2828
)
29-
settings.legacyWorkoutTargetRange = DoubleRange(minValue: 120, maxValue: 150)
29+
settings.legacyWorkoutTargetRange = DoubleRange(minValue: 120, maxValue: 150).quantityRange(for: .milligramsPerDeciliter)
3030
settings.enableLegacyWorkoutOverride(for: .infinity)
3131

3232
alert = nil

LoopTests/LoopSettingsTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import LoopKit
1212

1313

1414
class LoopSettingsTests: XCTestCase {
15-
private let preMealRange = DoubleRange(minValue: 80, maxValue: 80)
15+
private let preMealRange = DoubleRange(minValue: 80, maxValue: 80).quantityRange(for: .milligramsPerDeciliter)
1616
private let targetRange = DoubleRange(minValue: 95, maxValue: 105)
1717

1818
private lazy var settings: LoopSettings = {
@@ -29,7 +29,7 @@ class LoopSettingsTests: XCTestCase {
2929
var settings = self.settings
3030
let preMealStart = Date()
3131
settings.enablePreMealOverride(at: preMealStart, for: 1 /* hour */ * 60 * 60)
32-
let actualPreMealRange = settings.effectiveGlucoseTargetRangeSchedule()?.value(at: preMealStart.addingTimeInterval(30 /* minutes */ * 60))
32+
let actualPreMealRange = settings.effectiveGlucoseTargetRangeSchedule()?.quantityRange(at: preMealStart.addingTimeInterval(30 /* minutes */ * 60))
3333
XCTAssertEqual(preMealRange, actualPreMealRange)
3434
}
3535

@@ -81,19 +81,19 @@ class LoopSettingsTests: XCTestCase {
8181
)
8282
settings.scheduleOverride = override
8383

84-
let actualPreMealRange = settings.effectiveGlucoseTargetRangeSchedule()?.value(at: preMealStart.addingTimeInterval(30 /* minutes */ * 60))
84+
let actualPreMealRange = settings.effectiveGlucoseTargetRangeSchedule()?.quantityRange(at: preMealStart.addingTimeInterval(30 /* minutes */ * 60))
8585
XCTAssertEqual(actualPreMealRange, preMealRange)
8686

8787
// The pre-meal range should be projected into the future, despite the simultaneous schedule override
88-
let preMealRangeDuringOverride = settings.effectiveGlucoseTargetRangeSchedule()?.value(at: preMealStart.addingTimeInterval(2 /* hours */ * 60 * 60))
88+
let preMealRangeDuringOverride = settings.effectiveGlucoseTargetRangeSchedule()?.quantityRange(at: preMealStart.addingTimeInterval(2 /* hours */ * 60 * 60))
8989
XCTAssertEqual(preMealRangeDuringOverride, preMealRange)
9090
}
9191

9292
func testScheduleOverrideWithExpiredPreMealOverride() {
9393
var settings = self.settings
9494
settings.preMealOverride = TemporaryScheduleOverride(
9595
context: .preMeal,
96-
settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, targetRange: preMealRange),
96+
settings: TemporaryScheduleOverrideSettings(targetRange: preMealRange),
9797
startDate: Date(timeIntervalSinceNow: -2 /* hours */ * 60 * 60),
9898
duration: .finite(1 /* hours */ * 60 * 60),
9999
enactTrigger: .local,

LoopTests/ViewModels/BolusEntryViewModelTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ class BolusEntryViewModelTests: XCTestCase {
188188
XCTAssertNil(bolusEntryViewModel.preMealOverride)
189189
XCTAssertNil(bolusEntryViewModel.scheduleOverride)
190190
XCTAssertNil(bolusEntryViewModel.targetGlucoseSchedule)
191-
let newGlucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: .millimolesPerLiter, dailyItems: [
191+
let newGlucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: .milligramsPerDeciliter, dailyItems: [
192192
RepeatingScheduleValue(startTime: TimeInterval(0), value: DoubleRange(minValue: 100, maxValue: 110)),
193193
RepeatingScheduleValue(startTime: TimeInterval(28800), value: DoubleRange(minValue: 90, maxValue: 100)),
194194
RepeatingScheduleValue(startTime: TimeInterval(75600), value: DoubleRange(minValue: 100, maxValue: 110))
@@ -215,7 +215,7 @@ class BolusEntryViewModelTests: XCTestCase {
215215
XCTAssertNil(bolusEntryViewModel.preMealOverride)
216216
XCTAssertNil(bolusEntryViewModel.scheduleOverride)
217217
XCTAssertNil(bolusEntryViewModel.targetGlucoseSchedule)
218-
let newGlucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: .millimolesPerLiter, dailyItems: [
218+
let newGlucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: .milligramsPerDeciliter, dailyItems: [
219219
RepeatingScheduleValue(startTime: TimeInterval(0), value: DoubleRange(minValue: 100, maxValue: 110)),
220220
RepeatingScheduleValue(startTime: TimeInterval(28800), value: DoubleRange(minValue: 90, maxValue: 100)),
221221
RepeatingScheduleValue(startTime: TimeInterval(75600), value: DoubleRange(minValue: 100, maxValue: 110))

WatchApp Extension/Controllers/ActionHUDController.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import WatchKit
1010
import WatchConnectivity
11+
import HealthKit
1112
import LoopKit
1213
import LoopCore
1314
import SwiftUI
@@ -93,7 +94,7 @@ final class ActionHUDController: HUDInterfaceController {
9394
}
9495
}
9596

96-
glucoseFormatter.setPreferredNumberFormatter(for: loopManager.settings.glucoseUnit ?? .milligramsPerDeciliter)
97+
glucoseFormatter.setPreferredNumberFormatter(for: loopManager.displayGlucoseUnit)
9798
}
9899

99100
private var canEnableOverride: Bool {
@@ -138,7 +139,12 @@ final class ActionHUDController: HUDInterfaceController {
138139
}
139140

140141
let buttonToSelect = loopManager.settings.preMealOverride?.isActive() == true ? SelectedButton.on : SelectedButton.off
141-
let viewModel = OnOffSelectionViewModel(title: NSLocalizedString("Pre-Meal", comment: "Title for sheet to enable/disable pre-meal on watch"), message: formattedGlucoseRangeString(from: range), onSelection: setPreMealEnabled, selectedButton: buttonToSelect, selectedButtonTint: .carbsColor)
142+
let viewModel = OnOffSelectionViewModel(
143+
title: NSLocalizedString("Pre-Meal", comment: "Title for sheet to enable/disable pre-meal on watch"),
144+
message: formattedGlucoseRangeString(from: range),
145+
onSelection: setPreMealEnabled,
146+
selectedButton: buttonToSelect,
147+
selectedButtonTint: .carbsColor)
142148

143149
presentController(withName: OnOffSelectionController.className, context: viewModel)
144150
}
@@ -220,15 +226,17 @@ final class ActionHUDController: HUDInterfaceController {
220226
}
221227
}
222228

223-
private func formattedGlucoseRangeString(from range: DoubleRange) -> String {
224-
String(
229+
private func formattedGlucoseRangeString(from range: ClosedRange<HKQuantity>) -> String {
230+
let unit = loopManager.displayGlucoseUnit
231+
let rangeDouble = range.doubleRange(for: unit)
232+
return String(
225233
format: NSLocalizedString(
226234
"%1$@ – %2$@ %3$@",
227235
comment: "Format string for glucose range (1: lower bound)(2: upper bound)(3: unit)"
228236
),
229-
glucoseFormatter.numberFormatter.string(from: range.minValue) ?? String(range.minValue),
230-
glucoseFormatter.numberFormatter.string(from: range.maxValue) ?? String(range.maxValue),
231-
glucoseFormatter.string(from: loopManager.settings.glucoseUnit ?? .milligramsPerDeciliter)
237+
glucoseFormatter.numberFormatter.string(from: rangeDouble.minValue) ?? String(rangeDouble.minValue),
238+
glucoseFormatter.numberFormatter.string(from: rangeDouble.maxValue) ?? String(rangeDouble.maxValue),
239+
glucoseFormatter.string(from: unit)
232240
)
233241
}
234242

WatchApp Extension/Controllers/HUDInterfaceController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class HUDInterfaceController: WKInterfaceController {
7070
if date != nil {
7171
glucoseLabel.setText(NSLocalizedString("– – –", comment: "No glucose value representation (3 dashes for mg/dL)"))
7272
glucoseLabel.setHidden(false)
73-
if let glucose = activeContext.glucose, let glucoseDate = activeContext.glucoseDate, let unit = activeContext.preferredGlucoseUnit, glucoseDate.timeIntervalSinceNow > -LoopCoreConstants.inputDataRecencyInterval {
73+
if let glucose = activeContext.glucose, let glucoseDate = activeContext.glucoseDate, let unit = activeContext.displayGlucoseUnit, glucoseDate.timeIntervalSinceNow > -LoopCoreConstants.inputDataRecencyInterval {
7474
let formatter = NumberFormatter.glucoseFormatter(for: unit)
7575

7676
if let glucoseValue = formatter.string(from: glucose.doubleValue(for: unit)) {

WatchApp Extension/ExtensionDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ final class ExtensionDelegate: NSObject, WKExtensionDelegate {
161161
return
162162
}
163163

164-
if context.preferredGlucoseUnit == nil {
164+
if context.displayGlucoseUnit == nil {
165165
let type = HKQuantityType.quantityType(forIdentifier: .bloodGlucose)!
166166
loopManager.healthStore.preferredUnits(for: [type]) { (units, error) in
167-
context.preferredGlucoseUnit = units[type]
167+
context.displayGlucoseUnit = units[type]
168168

169169
DispatchQueue.main.async {
170170
self.loopManager.updateContext(context)

0 commit comments

Comments
 (0)