Skip to content

Commit 0a97649

Browse files
ps2rickpasettonhamming
authored
Tidepool merge (#1667)
* Reorder build phases for Xcode 13 * LOOP-3980 Avoid cancelling temp basal on launch. (#499) * Avoid cancelling temp basal on launch * Add test for ignoring initial value of isClosedLoop * COASTAL-651: Adds PersistedAlertStore for looking up outstanding alerts (#501) * Checkpoint: AlertSearcher * checkpoint * COASTAL-651: Adds PersistedAlertStore for looking up outstanding alerts Adds ability to query CoreData for all outstanding (i.e. "unretracted") alerts from a given `managerIdentifier`. https://tidepool.atlassian.net/browse/COASTAL-651 * unit tests * This makes issuing and retracting alerts synchronous, so when the functions return CoreData may be queried (This is not ideal, but in practice the performance hit will be minimal) * Better version of previous commit: make writes to AlertStore synchronous so subsequent reads get latest data. (Again, performance hit should be minimal) * PR Feedback * COASTAL-668: Fix crash when alert acknowledgement fails (#502) If alert acknowledgement fails, we display another in-app modal alert but need to do so on the main queue. https://tidepool.atlassian.net/browse/COASTAL-668 * COASTAL-625: Relax requirement that HUDProvider needs to provide LevelHUDView (#503) LevelHUDView is for providing a view that displays a thermometer level, which not all Pumps may provide (e.g. Coastal). This relaxes the requirement and just requires returning a BaseHUDView. https://tidepool.atlassian.net/browse/COASTAL-625 * [COASTAL-674] record already retracted alert and look up all alerts matching identifier (#504) * record already retracted alert and look up all alerts matching identifier * response to PR comments * Fix merge issues Co-authored-by: Rick Pasetto <[email protected]> Co-authored-by: Nathaniel Hamming <[email protected]>
1 parent af7c6de commit 0a97649

File tree

12 files changed

+442
-42
lines changed

12 files changed

+442
-42
lines changed

Common/Models/PumpManagerUI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import LoopKitUI
1212

1313
typealias PumpManagerHUDViewRawValue = [String: Any]
1414

15-
func PumpManagerHUDViewFromRawValue(_ rawValue: PumpManagerHUDViewRawValue, pluginManager: PluginManager) -> LevelHUDView? {
15+
func PumpManagerHUDViewFromRawValue(_ rawValue: PumpManagerHUDViewRawValue, pluginManager: PluginManager) -> BaseHUDView? {
1616
guard
1717
let identifier = rawValue["managerIdentifier"] as? String,
1818
let rawState = rawValue["hudProviderView"] as? HUDProvider.HUDViewRawState,

Loop Status Extension/StatusViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class StatusViewController: UIViewController, NCWidgetProviding {
223223
}
224224

225225
// Pump Status
226-
let pumpManagerHUDView: LevelHUDView
226+
let pumpManagerHUDView: BaseHUDView
227227
if let hudViewContext = context.pumpManagerHUDViewContext,
228228
let contextHUDView = PumpManagerHUDViewFromRawValue(hudViewContext.pumpManagerHUDViewRawValue, pluginManager: self.pluginManager)
229229
{

Loop/Managers/Alerts/AlertManager.swift

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,23 @@ extension AlertManager: AlertManagerResponder {
106106
}
107107

108108
func presentAcknowledgementFailedAlert(error: Error) {
109-
let message: String
110-
if let localizedError = error as? LocalizedError {
111-
message = [localizedError.localizedDescription, localizedError.recoverySuggestion].compactMap({$0}).joined(separator: "\n\n")
112-
} else {
113-
message = String(format: NSLocalizedString("%1$@ is unable to clear the alert from your device", comment: "Message for alert shown when alert acknowledgement fails for a device, and the device does not provide a LocalizedError. (1: app name)"), Bundle.main.bundleDisplayName)
109+
DispatchQueue.main.async {
110+
let message: String
111+
if let localizedError = error as? LocalizedError {
112+
message = [localizedError.localizedDescription, localizedError.recoverySuggestion].compactMap({$0}).joined(separator: "\n\n")
113+
} else {
114+
message = String(format: NSLocalizedString("%1$@ is unable to clear the alert from your device", comment: "Message for alert shown when alert acknowledgement fails for a device, and the device does not provide a LocalizedError. (1: app name)"), Bundle.main.bundleDisplayName)
115+
}
116+
self.log.info("Alert acknowledgement failed: %{public}@", message)
117+
118+
let alert = UIAlertController(
119+
title: NSLocalizedString("Unable To Clear Alert", comment: "Title for alert shown when alert acknowledgement fails"),
120+
message: message,
121+
preferredStyle: .alert)
122+
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action for alert when alert acknowledgment fails"), style: .default))
123+
124+
self.alertPresenter.present(alert, animated: true)
114125
}
115-
let alert = UIAlertController(
116-
title: NSLocalizedString("Unable To Clear Alert", comment: "Title for alert shown when alert acknowledgement fails"),
117-
message: message,
118-
preferredStyle: .alert)
119-
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action for alert when alert acknowledgment fails"), style: .default))
120-
121-
self.alertPresenter.present(alert, animated: true)
122126
}
123127
}
124128

@@ -186,7 +190,7 @@ extension AlertManager {
186190
}
187191

188192
private func playbackAlertsFromAlertStore() {
189-
alertStore.lookupAllUnacknowledged {
193+
alertStore.lookupAllUnacknowledgedUnretracted {
190194
switch $0 {
191195
case .failure(let error):
192196
self.log.error("Could not fetch unacknowledged alerts: %@", error.localizedDescription)
@@ -251,6 +255,70 @@ extension AlertManager {
251255
}
252256
}
253257

258+
// MARK: PersistedAlertStore
259+
extension AlertManager: PersistedAlertStore {
260+
public func doesIssuedAlertExist(identifier: Alert.Identifier, completion: @escaping (Result<Bool, Error>) -> Void) {
261+
alertStore.lookupAllMatching(identifier: identifier) { result in
262+
switch result {
263+
case .success(let storedAlerts):
264+
completion(.success(!storedAlerts.isEmpty))
265+
case .failure(let error):
266+
completion(.failure(error))
267+
}
268+
}
269+
}
270+
271+
public func lookupAllUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {
272+
alertStore.lookupAllUnretracted(managerIdentifier: managerIdentifier) {
273+
switch $0 {
274+
case .success(let alerts):
275+
do {
276+
let result = try alerts.map {
277+
PersistedAlert(
278+
alert: try Alert(from: $0, adjustedForStorageTime: false),
279+
issuedDate: $0.issuedDate,
280+
retractedDate: $0.retractedDate,
281+
acknowledgedDate: $0.acknowledgedDate
282+
)
283+
}
284+
completion(.success(result))
285+
} catch {
286+
completion(.failure(error))
287+
}
288+
case .failure(let error):
289+
completion(.failure(error))
290+
}
291+
}
292+
}
293+
294+
public func lookupAllUnacknowledgedUnretracted(managerIdentifier: String, completion: @escaping (Result<[PersistedAlert], Error>) -> Void) {
295+
alertStore.lookupAllUnacknowledgedUnretracted(managerIdentifier: managerIdentifier) {
296+
switch $0 {
297+
case .success(let alerts):
298+
do {
299+
let result = try alerts.map {
300+
PersistedAlert(
301+
alert: try Alert(from: $0, adjustedForStorageTime: false),
302+
issuedDate: $0.issuedDate,
303+
retractedDate: $0.retractedDate,
304+
acknowledgedDate: $0.acknowledgedDate
305+
)
306+
}
307+
completion(.success(result))
308+
} catch {
309+
completion(.failure(error))
310+
}
311+
case .failure(let error):
312+
completion(.failure(error))
313+
}
314+
}
315+
}
316+
317+
public func recordRetractedAlert(_ alert: Alert, at date: Date) {
318+
alertStore.recordRetractedAlert(alert, at: date)
319+
}
320+
}
321+
254322
// MARK: Extensions
255323

256324
fileprivate extension SyncAlertObject {

Loop/Managers/Alerts/AlertStore.swift

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class AlertStore {
8383
}
8484

8585
public func recordIssued(alert: Alert, at date: Date = Date(), completion: ((Result<Void, Error>) -> Void)? = nil) {
86-
self.managedObjectContext.perform {
86+
self.managedObjectContext.performAndWait {
8787
_ = StoredAlert(from: alert, context: self.managedObjectContext, issuedDate: date)
8888
do {
8989
try self.managedObjectContext.save()
@@ -97,6 +97,23 @@ public class AlertStore {
9797
}
9898
}
9999
}
100+
101+
public func recordRetractedAlert(_ alert: Alert, at date: Date, completion: ((Result<Void, Error>) -> Void)? = nil) {
102+
self.managedObjectContext.performAndWait {
103+
let storedAlert = StoredAlert(from: alert, context: self.managedObjectContext, issuedDate: date)
104+
storedAlert.retractedDate = date
105+
do {
106+
try self.managedObjectContext.save()
107+
self.log.default("Recorded retracted alert: %{public}@", alert.identifier.value)
108+
self.purgeExpired()
109+
self.delegate?.alertStoreHasUpdatedAlertData(self)
110+
completion?(.success)
111+
} catch {
112+
self.log.error("Could not store retracted alert: %{public}@, %{public}@", alert.identifier.value, String(describing: error))
113+
completion?(.failure(error))
114+
}
115+
}
116+
}
100117

101118
public func recordAcknowledgement(of identifier: Alert.Identifier, at date: Date = Date(),
102119
completion: ((Result<Void, Error>) -> Void)? = nil) {
@@ -125,15 +142,57 @@ public class AlertStore {
125142
},
126143
completion: completion)
127144
}
128-
129-
public func lookupAllUnacknowledged(completion: @escaping (Result<[StoredAlert], Error>) -> Void) {
145+
146+
public func lookupAllMatching(identifier: Alert.Identifier, completion: @escaping (Result<[StoredAlert], Error>) -> Void) {
130147
managedObjectContext.perform {
131148
do {
132149
let fetchRequest: NSFetchRequest<StoredAlert> = StoredAlert.fetchRequest()
133-
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
150+
let predicates = [
151+
NSPredicate(format: "managerIdentifier = %@", identifier.managerIdentifier),
152+
NSPredicate(format: "alertIdentifier = %@", identifier.alertIdentifier),
153+
]
154+
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
155+
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "modificationCounter", ascending: true) ]
156+
let result = try self.managedObjectContext.fetch(fetchRequest)
157+
completion(.success(result))
158+
} catch {
159+
completion(.failure(error))
160+
}
161+
}
162+
}
163+
164+
public func lookupAllUnretracted(managerIdentifier: String? = nil, completion: @escaping (Result<[StoredAlert], Error>) -> Void) {
165+
managedObjectContext.perform {
166+
do {
167+
let fetchRequest: NSFetchRequest<StoredAlert> = StoredAlert.fetchRequest()
168+
var predicates = [
169+
NSPredicate(format: "retractedDate == nil"),
170+
]
171+
if let managerIdentifier = managerIdentifier {
172+
predicates.insert(NSPredicate(format: "managerIdentifier = %@", managerIdentifier), at: 0)
173+
}
174+
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
175+
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "modificationCounter", ascending: true) ]
176+
let result = try self.managedObjectContext.fetch(fetchRequest)
177+
completion(.success(result))
178+
} catch {
179+
completion(.failure(error))
180+
}
181+
}
182+
}
183+
184+
public func lookupAllUnacknowledgedUnretracted(managerIdentifier: String? = nil, completion: @escaping (Result<[StoredAlert], Error>) -> Void) {
185+
managedObjectContext.perform {
186+
do {
187+
let fetchRequest: NSFetchRequest<StoredAlert> = StoredAlert.fetchRequest()
188+
var predicates = [
134189
NSPredicate(format: "acknowledgedDate == nil"),
135190
NSPredicate(format: "retractedDate == nil"),
136-
])
191+
]
192+
if let managerIdentifier = managerIdentifier {
193+
predicates.insert(NSPredicate(format: "managerIdentifier = %@", managerIdentifier), at: 0)
194+
}
195+
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
137196
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "modificationCounter", ascending: true) ]
138197
let result = try self.managedObjectContext.fetch(fetchRequest)
139198
completion(.success(result))
@@ -172,7 +231,7 @@ extension AlertStore {
172231
addingPredicate predicate: NSPredicate,
173232
with updateBlock: @escaping ManagedObjectUpdateBlock,
174233
completion: ((Result<Void, Error>) -> Void)?) {
175-
managedObjectContext.perform {
234+
managedObjectContext.performAndWait {
176235
self.lookupAll(identifier: identifier, predicate: predicate) {
177236
switch $0 {
178237
case .success(let objects):
@@ -194,7 +253,7 @@ extension AlertStore {
194253
addingPredicate predicate: NSPredicate,
195254
with updateBlock: @escaping ManagedObjectUpdateBlock,
196255
completion: ((Result<Void, Error>) -> Void)?) {
197-
managedObjectContext.perform {
256+
managedObjectContext.performAndWait {
198257
self.lookupLatest(identifier: identifier, predicate: predicate) {
199258
switch $0 {
200259
case .success(let object):

Loop/Managers/DeviceDataManager.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,28 @@ extension DeviceDataManager: AlertIssuer {
807807
}
808808
}
809809

810+
// MARK: - PersistedAlertStore
811+
extension DeviceDataManager: PersistedAlertStore {
812+
func doesIssuedAlertExist(identifier: Alert.Identifier, completion: @escaping (Swift.Result<Bool, Error>) -> Void) {
813+
precondition(alertManager != nil)
814+
alertManager.doesIssuedAlertExist(identifier: identifier, completion: completion)
815+
}
816+
func lookupAllUnretracted(managerIdentifier: String, completion: @escaping (Swift.Result<[PersistedAlert], Error>) -> Void) {
817+
precondition(alertManager != nil)
818+
alertManager.lookupAllUnretracted(managerIdentifier: managerIdentifier, completion: completion)
819+
}
820+
821+
func lookupAllUnacknowledgedUnretracted(managerIdentifier: String, completion: @escaping (Swift.Result<[PersistedAlert], Error>) -> Void) {
822+
precondition(alertManager != nil)
823+
alertManager.lookupAllUnacknowledgedUnretracted(managerIdentifier: managerIdentifier, completion: completion)
824+
}
825+
826+
func recordRetractedAlert(_ alert: Alert, at date: Date) {
827+
precondition(alertManager != nil)
828+
alertManager.recordRetractedAlert(alert, at: date)
829+
}
830+
}
831+
810832
// MARK: - CGMManagerDelegate
811833
extension DeviceDataManager: CGMManagerDelegate {
812834
func cgmManagerWantsDeletion(_ manager: CGMManager) {

Loop/Managers/LoopDataManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ final class LoopDataManager: LoopSettingsAlerterDelegate {
178178
// Cancel any active temp basal when going into closed loop off mode
179179
// The dispatch is necessary in case this is coming from a didSet already on the settings struct.
180180
self.automaticDosingStatus.$isClosedLoop
181-
.removeDuplicates()
182181
.dropFirst()
182+
.removeDuplicates()
183183
.receive(on: DispatchQueue.main)
184184
.sink { if !$0 {
185185
self.mutateSettings { settings in

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1511,7 +1511,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
15111511
}
15121512
}
15131513

1514-
private func addPumpManagerViewToHUD(_ view: LevelHUDView) {
1514+
private func addPumpManagerViewToHUD(_ view: BaseHUDView) {
15151515
if let hudView = hudView {
15161516
view.stateColors = .pumpStatus
15171517
hudView.addPumpManagerProvidedHUDView(view)

0 commit comments

Comments
 (0)