Skip to content

Commit 2f732c4

Browse files
authored
Loop alerting for uncertain delivery. (#228)
* Update to pumpmanager protocol changes * Present uncertainty alert, and recovery view from pump manager * Fix alert presentation on app restart * Fix tests * Re-present alert if user dismisses recovery ui * Refactor out view and alert logic for uncertain delivery * Add test for PumpManagerError.uncertainDelivery
1 parent a46eacc commit 2f732c4

15 files changed

+198
-254
lines changed

Loop.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,6 @@
380380
A98556852493F901000FD662 /* AlertStore+SimulatedCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98556842493F901000FD662 /* AlertStore+SimulatedCoreData.swift */; };
381381
A999D40424663CE1004C89D4 /* DoseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A999D40324663CE1004C89D4 /* DoseStore.swift */; };
382382
A999D40624663D18004C89D4 /* PumpManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A999D40524663D18004C89D4 /* PumpManagerError.swift */; };
383-
A999D40824663D6D004C89D4 /* SetBolusError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A999D40724663D6D004C89D4 /* SetBolusError.swift */; };
384383
A999D40A24663DC7004C89D4 /* CarbStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A999D40924663DC7004C89D4 /* CarbStore.swift */; };
385384
A9A63F8D246B261100588D5B /* DosingDecisionStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A63F8C246B261100588D5B /* DosingDecisionStoreTests.swift */; };
386385
A9A63F8E246B271600588D5B /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; };
@@ -398,7 +397,6 @@
398397
A9E6DFE6246A042E005B1A1C /* CarbStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E6DFE5246A042E005B1A1C /* CarbStoreTests.swift */; };
399398
A9E6DFE8246A043D005B1A1C /* DoseStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E6DFE7246A043C005B1A1C /* DoseStoreTests.swift */; };
400399
A9E6DFEA246A0448005B1A1C /* PumpManagerErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E6DFE9246A0448005B1A1C /* PumpManagerErrorTests.swift */; };
401-
A9E6DFEC246A0453005B1A1C /* SetBolusErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E6DFEB246A0453005B1A1C /* SetBolusErrorTests.swift */; };
402400
A9E6DFEF246A0474005B1A1C /* LoopErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E6DFEE246A0474005B1A1C /* LoopErrorTests.swift */; };
403401
A9F66FC3247F451500096EA7 /* UIDevice+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F66FC2247F451500096EA7 /* UIDevice+Loop.swift */; };
404402
A9F703732489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F703722489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift */; };
@@ -436,6 +434,8 @@
436434
C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */; };
437435
C136AA2423109CC6008A320D /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; };
438436
C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; };
437+
C13DA2B024F6C7690098BB29 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13DA2AF24F6C7690098BB29 /* UIViewController.swift */; };
438+
C148CEE724FD91BD00711B3B /* DeliveryUncertaintyAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C148CEE624FD91BD00711B3B /* DeliveryUncertaintyAlertManager.swift */; };
439439
C165B8CE23302C5D0004112E /* RemoteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C165B8CD23302C5D0004112E /* RemoteCommand.swift */; };
440440
C16DA84222E8E112008624C2 /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; };
441441
C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824991E1999FA00D9D25C /* CaseCountable.swift */; };
@@ -1162,7 +1162,6 @@
11621162
A98556842493F901000FD662 /* AlertStore+SimulatedCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlertStore+SimulatedCoreData.swift"; sourceTree = "<group>"; };
11631163
A999D40324663CE1004C89D4 /* DoseStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoseStore.swift; sourceTree = "<group>"; };
11641164
A999D40524663D18004C89D4 /* PumpManagerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManagerError.swift; sourceTree = "<group>"; };
1165-
A999D40724663D6D004C89D4 /* SetBolusError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetBolusError.swift; sourceTree = "<group>"; };
11661165
A999D40924663DC7004C89D4 /* CarbStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbStore.swift; sourceTree = "<group>"; };
11671166
A9A63F8C246B261100588D5B /* DosingDecisionStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DosingDecisionStoreTests.swift; sourceTree = "<group>"; };
11681167
A9B607AF247F000F00792BE4 /* UserNotifications+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserNotifications+Loop.swift"; sourceTree = "<group>"; };
@@ -1179,7 +1178,6 @@
11791178
A9E6DFE5246A042E005B1A1C /* CarbStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbStoreTests.swift; sourceTree = "<group>"; };
11801179
A9E6DFE7246A043C005B1A1C /* DoseStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoseStoreTests.swift; sourceTree = "<group>"; };
11811180
A9E6DFE9246A0448005B1A1C /* PumpManagerErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManagerErrorTests.swift; sourceTree = "<group>"; };
1182-
A9E6DFEB246A0453005B1A1C /* SetBolusErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetBolusErrorTests.swift; sourceTree = "<group>"; };
11831181
A9E6DFEE246A0474005B1A1C /* LoopErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopErrorTests.swift; sourceTree = "<group>"; };
11841182
A9F66FC2247F451500096EA7 /* UIDevice+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Loop.swift"; sourceTree = "<group>"; };
11851183
A9F703722489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbStore+SimulatedCoreData.swift"; sourceTree = "<group>"; };
@@ -1215,6 +1213,8 @@
12151213
C12CB9B623106A6200F84978 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Intents.strings; sourceTree = "<group>"; };
12161214
C12CB9B823106A6300F84978 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Intents.strings; sourceTree = "<group>"; };
12171215
C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_very_low_end_in_range.json; sourceTree = "<group>"; };
1216+
C13DA2AF24F6C7690098BB29 /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
1217+
C148CEE624FD91BD00711B3B /* DeliveryUncertaintyAlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryUncertaintyAlertManager.swift; sourceTree = "<group>"; };
12181218
C165B8CD23302C5D0004112E /* RemoteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteCommand.swift; sourceTree = "<group>"; };
12191219
C16DA84122E8E112008624C2 /* LoopPlugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopPlugins.swift; sourceTree = "<group>"; };
12201220
C17824991E1999FA00D9D25C /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = "<group>"; };
@@ -1778,7 +1778,6 @@
17781778
A9F703762489D8AA00C98AD8 /* PersistentDeviceLog+SimulatedCoreData.swift */,
17791779
A999D40524663D18004C89D4 /* PumpManagerError.swift */,
17801780
892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */,
1781-
A999D40724663D6D004C89D4 /* SetBolusError.swift */,
17821781
A9CBE45B248ACC03008E7BA2 /* SettingsStore+SimulatedCoreData.swift */,
17831782
C1FB428B217806A300FAB378 /* StateColorPalette.swift */,
17841783
43F89CA222BDFBBC006BB54E /* UIActivityIndicatorView.swift */,
@@ -1787,6 +1786,7 @@
17871786
8968B1112408B3520074BB48 /* UIFont.swift */,
17881787
437CEEE31CDE5C0A003C8C80 /* UIImage.swift */,
17891788
434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */,
1789+
C13DA2AF24F6C7690098BB29 /* UIViewController.swift */,
17901790
430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */,
17911791
A9B607AF247F000F00792BE4 /* UserNotifications+Loop.swift */,
17921792
);
@@ -1861,6 +1861,7 @@
18611861
4F70C20F1DE8FAC5006380B7 /* StatusExtensionDataManager.swift */,
18621862
89ADE13A226BFA0F0067222B /* TestingScenariosManager.swift */,
18631863
4328E0341CFC0AE100E199AA /* WatchDataManager.swift */,
1864+
C148CEE624FD91BD00711B3B /* DeliveryUncertaintyAlertManager.swift */,
18641865
);
18651866
path = Managers;
18661867
sourceTree = "<group>";
@@ -2191,7 +2192,6 @@
21912192
A9E6DFE7246A043C005B1A1C /* DoseStoreTests.swift */,
21922193
A9A63F8C246B261100588D5B /* DosingDecisionStoreTests.swift */,
21932194
A9E6DFE9246A0448005B1A1C /* PumpManagerErrorTests.swift */,
2194-
A9E6DFEB246A0453005B1A1C /* SetBolusErrorTests.swift */,
21952195
);
21962196
path = Extensions;
21972197
sourceTree = "<group>";
@@ -3173,6 +3173,7 @@
31733173
1D4A3E2E2478628500FD601B /* StoredAlert+CoreDataProperties.swift in Sources */,
31743174
E95D380124EADE7C005E2F50 /* DoseStoreProtocol.swift in Sources */,
31753175
43D381621EBD9759007F8C8F /* HeaderValuesTableViewCell.swift in Sources */,
3176+
C13DA2B024F6C7690098BB29 /* UIViewController.swift in Sources */,
31763177
A9C62D892331703100535612 /* LoggingServicesManager.swift in Sources */,
31773178
89E267FF229267DF00A3F2AF /* Optional.swift in Sources */,
31783179
43785E982120E7060057DED1 /* Intents.intentdefinition in Sources */,
@@ -3185,6 +3186,7 @@
31853186
C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */,
31863187
4F08DE8F1E7BB871006741EA /* CollectionType+Loop.swift in Sources */,
31873188
A9F703772489D8AA00C98AD8 /* PersistentDeviceLog+SimulatedCoreData.swift in Sources */,
3189+
C148CEE724FD91BD00711B3B /* DeliveryUncertaintyAlertManager.swift in Sources */,
31883190
435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */,
31893191
1DDE273E24AEA4B000796622 /* SettingsView.swift in Sources */,
31903192
A9B607B0247F000F00792BE4 /* UserNotifications+Loop.swift in Sources */,
@@ -3227,7 +3229,6 @@
32273229
439706E622D2E84900C81566 /* PredictionSettingTableViewCell.swift in Sources */,
32283230
430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */,
32293231
43A51E211EB6DBDD000736CC /* LoopChartsTableViewController.swift in Sources */,
3230-
A999D40824663D6D004C89D4 /* SetBolusError.swift in Sources */,
32313232
8968B1122408B3520074BB48 /* UIFont.swift in Sources */,
32323233
438D42FB1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift in Sources */,
32333234
89A1B66E24ABFDF800117AC2 /* SupportedBolusVolumesUserInfo.swift in Sources */,
@@ -3427,7 +3428,6 @@
34273428
A9E6DFE6246A042E005B1A1C /* CarbStoreTests.swift in Sources */,
34283429
A9DAE7D02332D77F006AE942 /* LoopTests.swift in Sources */,
34293430
E93E86B024DDE1BD00FF40C8 /* MockGlucoseStore.swift in Sources */,
3430-
A9E6DFEC246A0453005B1A1C /* SetBolusErrorTests.swift in Sources */,
34313431
1DFE9E172447B6270082C280 /* UserNotificationAlertPresenterTests.swift in Sources */,
34323432
A9E6DFE8246A043D005B1A1C /* DoseStoreTests.swift in Sources */,
34333433
E93E86B224DDE21D00FF40C8 /* MockCarbStore.swift in Sources */,

Loop/AppDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, DeviceOrientationCo
3838
bluetoothStateManager = BluetoothStateManager()
3939
bluetoothStateManager.addBluetoothStateObserver(rootViewController.rootViewController)
4040
alertManager = AlertManager(rootViewController: rootViewController, expireAfter: Bundle.main.localCacheDuration ?? .days(1))
41-
deviceDataManager = DeviceDataManager(pluginManager: pluginManager, alertManager: alertManager, bluetoothStateManager: bluetoothStateManager)
41+
deviceDataManager = DeviceDataManager(pluginManager: pluginManager, alertManager: alertManager, bluetoothStateManager: bluetoothStateManager, rootViewController: rootViewController)
4242
loopAlertsManager = LoopAlertsManager(alertManager: alertManager, bluetoothStateManager: bluetoothStateManager)
4343

4444
SharedLogging.instance = deviceDataManager.loggingServicesManager

Loop/Extensions/PumpManagerError.swift

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,46 @@ import LoopKit
1010

1111
extension PumpManagerError: Codable {
1212
public init(from decoder: Decoder) throws {
13-
let container = try decoder.container(keyedBy: CodableKeys.self)
14-
if let associated = try container.decodeIfPresent(Associated.self, forKey: .configuration) {
15-
self = .configuration(associated.localizedError)
16-
} else if let associated = try container.decodeIfPresent(Associated.self, forKey: .connection) {
17-
self = .connection(associated.localizedError)
18-
} else if let associated = try container.decodeIfPresent(Associated.self, forKey: .communication) {
19-
self = .communication(associated.localizedError)
20-
} else if let associated = try container.decodeIfPresent(Associated.self, forKey: .deviceState) {
21-
self = .deviceState(associated.localizedError)
13+
if let string = try? decoder.singleValueContainer().decode(String.self) {
14+
switch string {
15+
case CodableKeys.uncertainDelivery.rawValue:
16+
self = .uncertainDelivery
17+
default:
18+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
19+
}
2220
} else {
23-
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
21+
let container = try decoder.container(keyedBy: CodableKeys.self)
22+
if let associated = try container.decodeIfPresent(Associated.self, forKey: .configuration) {
23+
self = .configuration(associated.localizedError)
24+
} else if let associated = try container.decodeIfPresent(Associated.self, forKey: .connection) {
25+
self = .connection(associated.localizedError)
26+
} else if let associated = try container.decodeIfPresent(Associated.self, forKey: .communication) {
27+
self = .communication(associated.localizedError)
28+
} else if let associated = try container.decodeIfPresent(Associated.self, forKey: .deviceState) {
29+
self = .deviceState(associated.localizedError)
30+
} else {
31+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "invalid enumeration"))
32+
}
2433
}
2534
}
2635

2736
public func encode(to encoder: Encoder) throws {
28-
var container = encoder.container(keyedBy: CodableKeys.self)
2937
switch self {
3038
case .configuration(let localizedError):
39+
var container = encoder.container(keyedBy: CodableKeys.self)
3140
try container.encode(Associated(localizedError: localizedError), forKey: .configuration)
3241
case .connection(let localizedError):
42+
var container = encoder.container(keyedBy: CodableKeys.self)
3343
try container.encode(Associated(localizedError: localizedError), forKey: .connection)
3444
case .communication(let localizedError):
45+
var container = encoder.container(keyedBy: CodableKeys.self)
3546
try container.encode(Associated(localizedError: localizedError), forKey: .communication)
3647
case .deviceState(let localizedError):
48+
var container = encoder.container(keyedBy: CodableKeys.self)
3749
try container.encode(Associated(localizedError: localizedError), forKey: .deviceState)
50+
case .uncertainDelivery:
51+
var container = encoder.singleValueContainer()
52+
try container.encode(CodableKeys.uncertainDelivery.rawValue)
3853
}
3954
}
4055

@@ -51,5 +66,6 @@ extension PumpManagerError: Codable {
5166
case connection
5267
case communication
5368
case deviceState
69+
case uncertainDelivery
5470
}
5571
}

Loop/Extensions/SetBolusError.swift

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// UIViewController.swift
3+
// Loop
4+
//
5+
// Created by Pete Schwamb on 8/26/20.
6+
// Copyright © 2020 LoopKit Authors. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import UIKit
11+
12+
extension UIViewController {
13+
var topmostViewController: UIViewController {
14+
if let tabController = self as? UITabBarController {
15+
return tabController.selectedViewController?.topmostViewController ?? self
16+
}
17+
if let navController = self as? UINavigationController {
18+
return navController.visibleViewController?.topmostViewController ?? self
19+
}
20+
return presentedViewController?.topmostViewController ?? self
21+
}
22+
}

Loop/Managers/Alerts/InAppModalAlertPresenter.swift

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -140,24 +140,10 @@ extension InAppModalAlertPresenter {
140140
// For now, this is a simple alert with an "OK" button
141141
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
142142
alertController.addAction(newActionFunc(action, isCritical ? .destructive : .default, { _ in completion() }))
143-
topViewController(controller: rootViewController)?.present(alertController, animated: true)
143+
rootViewController?.topmostViewController.present(alertController, animated: true)
144144
return alertController
145145
}
146-
147-
// Helper function pulled from SO...may be outdated, especially in the SwiftUI world
148-
private func topViewController(controller: UIViewController?) -> UIViewController? {
149-
if let tabController = controller as? UITabBarController {
150-
return topViewController(controller: tabController.selectedViewController)
151-
}
152-
if let navController = controller as? UINavigationController {
153-
return topViewController(controller: navController.visibleViewController)
154-
}
155-
if let presented = controller?.presentedViewController {
156-
return topViewController(controller: presented)
157-
}
158-
return controller
159-
}
160-
146+
161147
private func playSound(for alert: Alert) {
162148
guard let sound = alert.sound else { return }
163149
switch sound {

0 commit comments

Comments
 (0)