Skip to content

Commit 3460924

Browse files
committed
Adding rate helper
1 parent b6477af commit 3460924

9 files changed

+83
-16
lines changed

LoopKit/QuantityFormatter.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ public extension HKUnit {
187187
HKUnit.millimolesPerLiter.unitDivided(by: .internationalUnit()),
188188
HKUnit.millimolesPerLiter.unitDivided(by: .minute()):
189189
return 1
190+
case .milligramsPerDeciliterPerMinute:
191+
return 1
190192
default:
191193
return 0
192194
}
@@ -336,6 +338,29 @@ public extension HKUnit {
336338
break // Fallback to the MeasurementFormatter localization
337339
}
338340
}
341+
342+
if self == HKUnit.millimolesPerLiterPerMinute {
343+
switch style {
344+
case .short, .medium:
345+
return LocalizedString("mmol/L/min", comment: "The short unit display string for millimoles per liter per minute")
346+
case .long:
347+
fallthrough
348+
@unknown default:
349+
break // Fallback to the MeasurementFormatter localization
350+
}
351+
}
352+
353+
if self == HKUnit.milligramsPerDeciliterPerMinute {
354+
switch style {
355+
case .short, .medium:
356+
return LocalizedString("mg/dL/min", comment: "The short unit display string for milligrams per liter per minute")
357+
case .long:
358+
fallthrough
359+
@unknown default:
360+
break // Fallback to the MeasurementFormatter localization
361+
}
362+
}
363+
339364

340365
return nil
341366
}

LoopKitTests/HKUnitTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class HKUnitTests: XCTestCase {
2222
XCTAssertEqual(HKUnit.gram().unitDivided(by: .internationalUnit()).preferredFractionDigits, 0)
2323
XCTAssertEqual(HKUnit.milligramsPerDeciliter.preferredFractionDigits, 0)
2424
XCTAssertEqual(HKUnit.milligramsPerDeciliter.unitDivided(by: .internationalUnit()).preferredFractionDigits, 0)
25-
XCTAssertEqual(HKUnit.milligramsPerDeciliter.unitDivided(by: .minute()).preferredFractionDigits, 0)
25+
XCTAssertEqual(HKUnit.milligramsPerDeciliter.unitDivided(by: .minute()).preferredFractionDigits, 1)
2626
XCTAssertEqual(HKUnit.internationalUnit().preferredFractionDigits, 0)
2727
XCTAssertEqual(HKUnit.internationalUnit().unitDivided(by: .hour()).preferredFractionDigits, 0)
2828
}
@@ -47,7 +47,7 @@ class HKUnitTests: XCTestCase {
4747
XCTAssertEqual(HKUnit.gram().maxFractionDigits, 0)
4848
XCTAssertEqual(HKUnit.milligramsPerDeciliter.maxFractionDigits, 0)
4949
XCTAssertEqual(HKUnit.milligramsPerDeciliter.unitDivided(by: .internationalUnit()).maxFractionDigits, 0)
50-
XCTAssertEqual(HKUnit.milligramsPerDeciliter.unitDivided(by: .minute()).maxFractionDigits, 0)
50+
XCTAssertEqual(HKUnit.milligramsPerDeciliter.unitDivided(by: .minute()).maxFractionDigits, 1)
5151
}
5252

5353
func testPickerFractionDigits() throws {

LoopKitTests/QuantityFormatterTests.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,29 @@ class QuantityFormatterTests: XCTestCase {
126126
XCTAssertEqual("0.0 millimoles per liter", formatter.string(from: HKQuantity(unit: unit, doubleValue: 0))!)
127127
XCTAssertEqual("1.0 millimoles per liter", formatter.string(from: HKQuantity(unit: unit, doubleValue: 1))!)
128128
}
129-
129+
130+
func testGlucoseRates() {
131+
var unit = HKUnit.millimolesPerLiterPerMinute
132+
setFormatter(for: unit)
133+
134+
XCTAssertEqual("mmol/L/min", formatter.localizedUnitStringWithPlurality())
135+
XCTAssertEqual("0.5 mmol/L/min", formatter.string(from: HKQuantity(unit: unit, doubleValue: 0.5))!)
136+
XCTAssertEqual("0.8 mmol/L/min", formatter.string(from: HKQuantity(unit: unit, doubleValue: 0.8))!)
137+
XCTAssertEqual("1.5 mmol/L/min", formatter.string(from: HKQuantity(unit: unit, doubleValue: 1.5))!)
138+
139+
unit = HKUnit.milligramsPerDeciliterPerMinute
140+
setFormatter(for: unit)
141+
142+
XCTAssertEqual("mg/dL/min", formatter.localizedUnitStringWithPlurality())
143+
XCTAssertEqual("1.0 mg/dL/min", formatter.string(from: HKQuantity(unit: unit, doubleValue: 1.0))!)
144+
XCTAssertEqual("5.2 mg/dL/min", formatter.string(from: HKQuantity(unit: unit, doubleValue: 5.2))!)
145+
XCTAssertEqual("10.1 mg/dL/min", formatter.string(from: HKQuantity(unit: unit, doubleValue: 10.1))!)
146+
147+
}
148+
130149
func testAvoidLineBreaking() {
131-
formatter.avoidLineBreaking = true
132150
setFormatter(for: .internationalUnit())
151+
formatter.avoidLineBreaking = true
133152
XCTAssertEqual("U", formatter.localizedUnitStringWithPlurality())
134153
XCTAssertEqual("10\(String.nonBreakingSpace)U", formatter.string(from: HKQuantity(unit: HKUnit.internationalUnit(), doubleValue: 10))!)
135154
formatter.unitStyle = .short
@@ -140,6 +159,7 @@ class QuantityFormatterTests: XCTestCase {
140159

141160
formatter.unitStyle = .medium
142161
setFormatter(for: HKUnit.milligramsPerDeciliter)
162+
formatter.avoidLineBreaking = true
143163
XCTAssertEqual("mg\(String.wordJoiner)/\(String.wordJoiner)dL", formatter.localizedUnitStringWithPlurality())
144164
XCTAssertEqual("60\(String.nonBreakingSpace)mg\(String.wordJoiner)/\(String.wordJoiner)dL", formatter.string(from: HKQuantity(unit: HKUnit.milligramsPerDeciliter, doubleValue: 60))!)
145165
XCTAssertEqual("180\(String.nonBreakingSpace)mg\(String.wordJoiner)/\(String.wordJoiner)dL", formatter.string(from: HKQuantity(unit: HKUnit.milligramsPerDeciliter, doubleValue: 180))!)
@@ -155,6 +175,7 @@ class QuantityFormatterTests: XCTestCase {
155175

156176
formatter.unitStyle = .medium
157177
setFormatter(for: HKUnit.millimolesPerLiter)
178+
formatter.avoidLineBreaking = true
158179
XCTAssertEqual("mmol\(String.wordJoiner)/\(String.wordJoiner)L", formatter.localizedUnitStringWithPlurality())
159180
XCTAssertEqual("6.0\(String.nonBreakingSpace)mmol\(String.wordJoiner)/\(String.wordJoiner)L", formatter.string(from: HKQuantity(unit: HKUnit.millimolesPerLiter, doubleValue: 6))!)
160181
XCTAssertEqual("7.8\(String.nonBreakingSpace)mmol\(String.wordJoiner)/\(String.wordJoiner)L", formatter.string(from: HKQuantity(unit: HKUnit.millimolesPerLiter, doubleValue: 7.84))!)

LoopKitUI/CGMManagerUI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ extension CGMManagerUI {
6161
}
6262

6363
/// When conformance to the DisplayGlucoseUnitObserver is desired, use this function to be notified when the user display glucose unit changes
64-
public func displayGlucoseUnitDidChange(to displayGlucoseUnit: HKUnit) {
64+
public func unitDidChange(to displayGlucoseUnit: HKUnit) {
6565
// optional
6666
}
6767
}

LoopKitUI/DisplayGlucoseUnitObserver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ import Foundation
1010
import HealthKit
1111

1212
public protocol DisplayGlucoseUnitObserver {
13-
func displayGlucoseUnitDidChange(to displayGlucoseUnit: HKUnit)
13+
func unitDidChange(to displayGlucoseUnit: HKUnit)
1414
}

LoopKitUI/ViewModels/DisplayGlucosePreference.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,40 @@ import LoopKit
1414
public class DisplayGlucosePreference: ObservableObject {
1515
@Published public private(set) var unit: HKUnit
1616
@Published public private(set) var formatter: QuantityFormatter
17+
@Published public private(set) var minuteRateFormatter: QuantityFormatter
1718

1819
public init(displayGlucoseUnit: HKUnit) {
1920
self.unit = displayGlucoseUnit
2021
self.formatter = QuantityFormatter(for: displayGlucoseUnit)
22+
self.minuteRateFormatter = QuantityFormatter(for: displayGlucoseUnit.unitDivided(by: .minute()))
23+
self.formatter.numberFormatter.notANumberSymbol = ""
24+
self.minuteRateFormatter.numberFormatter.notANumberSymbol = ""
2125
}
26+
27+
/// Formats a glucose HKQuantity and unit as a localized string
28+
///
29+
/// - Parameters:
30+
/// - quantity: The quantity
31+
/// - includeUnit: Whether or not to include the unit in the returned string
32+
/// - Returns: A localized string, or the numberFormatter's notANumberSymbol (default is "–")
33+
open func format(_ quantity: HKQuantity, includeUnit: Bool = true) -> String {
34+
return formatter.string(from: quantity, includeUnit: includeUnit) ?? self.formatter.numberFormatter.notANumberSymbol
35+
}
36+
37+
/// Formats a glucose HKQuantity rate (in terms of mg/dL/min or mmol/L/min and unit as a localized string
38+
///
39+
/// - Parameters:
40+
/// - quantity: The quantity
41+
/// - includeUnit: Whether or not to include the unit in the returned string
42+
/// - Returns: A localized string, or the numberFormatter's notANumberSymbol (default is "–")
43+
open func formatMinuteRate(_ quantity: HKQuantity, includeUnit: Bool = true) -> String {
44+
return minuteRateFormatter.string(from: quantity, includeUnit: includeUnit) ?? self.formatter.numberFormatter.notANumberSymbol
45+
}
46+
2247
}
2348

2449
extension DisplayGlucosePreference: DisplayGlucoseUnitObserver {
25-
public func displayGlucoseUnitDidChange(to displayGlucoseUnit: HKUnit) {
50+
public func unitDidChange(to displayGlucoseUnit: HKUnit) {
2651
self.unit = displayGlucoseUnit
2752
self.formatter = QuantityFormatter(for: displayGlucoseUnit)
2853
}

MockKitUI/MockCGMManager+UI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension MockCGMManager: CGMManagerUI {
2929
}
3030

3131
public func settingsViewController(bluetoothProvider: BluetoothProvider, displayGlucosePreference: DisplayGlucosePreference, colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> CGMManagerViewController {
32-
let settings = MockCGMManagerSettingsView(cgmManager: self, displayGlucoseUnitObservable: displayGlucoseUnitObservable, appName: appName)
32+
let settings = MockCGMManagerSettingsView(cgmManager: self, displayGlucosePreference: displayGlucosePreference, appName: appName)
3333
let hostingController = DismissibleHostingController(rootView: settings, colorPalette: colorPalette)
3434
hostingController.navigationItem.backButtonDisplayMode = .generic
3535
let nav = CGMManagerSettingsNavigationViewController(rootViewController: hostingController)

MockKitUI/ViewModel/MockCGMManagerSettingsViewModel.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,7 @@ class MockCGMManagerSettingsViewModel: ObservableObject {
9191
return
9292
}
9393
let glucoseUnitPerMinute = displayGlucosePreference.unit.unitDivided(by: .minute())
94-
// This seemingly strange replacement of glucose units is only to display the unit string correctly
95-
let trendPerMinute = HKQuantity(unit: displayGlucosePreference.unit, doubleValue: trendRate.doubleValue(for: glucoseUnitPerMinute))
96-
if let formatted = displayGlucosePreference.formatter.string(from: trendPerMinute) {
97-
lastGlucoseTrendFormatted = String(format: LocalizedString("%@/min", comment: "Format string for glucose trend per minute. (1: glucose value and unit)"), formatted)
98-
}
94+
lastGlucoseTrendFormatted = displayGlucosePreference.formatMinuteRate(trendRate)
9995
}
10096

10197
func setLastGlucoseValue(_ lastGlucose: HKQuantity?) {
@@ -105,8 +101,8 @@ class MockCGMManagerSettingsViewModel: ObservableObject {
105101
return
106102
}
107103

108-
lastGlucoseValueWithUnitFormatted = displayGlucosePreference.formatter.string(from: lastGlucose)
109-
lastGlucoseValueFormatted = displayGlucosePreference.formatter.string(from: lastGlucose, includeUnit: false) ?? "---"
104+
lastGlucoseValueWithUnitFormatted = displayGlucosePreference.format(lastGlucose)
105+
lastGlucoseValueFormatted = displayGlucosePreference.format(lastGlucose, includeUnit: false)
110106
}
111107
}
112108

MockKitUI/Views/InsulinStatusView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct InsulinStatusView: View {
4040
return 65 + subViewSpacing
4141
}
4242

43-
let basalRateFormatter = QuantityFormatter(for: .internationalUnit())
43+
let basalRateFormatter = QuantityFormatter(for: .internationalUnitsPerHour)
4444

4545
private var inNoDelivery: Bool {
4646
!viewModel.isDeliverySuspended && viewModel.basalDeliveryRate == nil

0 commit comments

Comments
 (0)