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
27 changes: 13 additions & 14 deletions G7SensorKit/G7CGMManager/G7CGMManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -599,25 +599,24 @@ extension G7CGMManager {
/// cache window, and at launch it replays any past-due delayed alert as
/// immediate until the user acknowledges it. A deleted CGM's scheduled
/// expiry reminders would keep firing for weeks (LibreLoop #13). The
/// retractions are queued on the delegate queue ahead of the deletion
/// notification, so they land before Loop releases us. LoopKit's default
/// `delete` only notifies, so the notification is re-issued here.
public func delete(completion: @escaping () -> Void) {
/// retractions are awaited before the delegate is asked to delete us.
public func delete() async {
cancelSuspectedSessionEndScan()
// A manager passed its configuration lets go of the sensor and nothing more: the sensor's
// lifecycle and records belong to the controller that passed it.
guard !state.configuredByAnotherController else {
if state.configuredByAnotherController {
sensor.stopScanning()
retractAllLifecycleAlerts()
notifyDelegateOfDeletion(completion: completion)
return
} else {
signOutOfShare()
sensor.stopScanning()
recordSensorEndIfNeeded()
archiveCurrentSensor(reason: .deleted)
}
signOutOfShare()
sensor.stopScanning()
retractAllLifecycleAlerts()
recordSensorEndIfNeeded()
archiveCurrentSensor(reason: .deleted)
notifyDelegateOfDeletion(completion: completion)
let delegate = cgmManagerDelegate
for alert in G7LifecycleAlert.allCases {
await delegate?.retractAlert(identifier: alert.identifier(managerIdentifier: pluginIdentifier))
}
await delegate?.cgmManagerWantsDeletion(self)
}

// MARK: - Session events
Expand Down
6 changes: 2 additions & 4 deletions G7SensorKitTests/G7DeviceHandoffTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,9 @@ final class G7DeviceHandoffTests: XCTestCase {
XCTAssertNil(manager.state.suspectedSessionEndAt)
}

func testDeletingAPassedConfigurationRecordsNoSensorEnd() {
func testDeletingAPassedConfigurationRecordsNoSensorEnd() async {
let manager = makeManager(state: G7CGMManagerState.adopted(from: makeManager(state: phoneState).exportConfiguration().state))
let deleted = expectation(description: "deleted")
manager.delete { deleted.fulfill() }
wait(for: [deleted], timeout: 2)
await manager.delete()
XCTAssertNil(manager.state.sensorEndRecordedFor, "the sensor's lifecycle is the other controller's")
XCTAssertNil(manager.state.previousSensor)
XCTAssertEqual(manager.state.sensorID, sensorID)
Expand Down
15 changes: 5 additions & 10 deletions G7SensorKitTests/G7LifecycleAlertTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,18 +256,16 @@ class G7LifecycleAlertManagerTests: XCTestCase {
/// replayed by Loop at every launch until acknowledged. Deleting must
/// retract every alert this manager can issue, and must do so before the
/// deletion notification that makes Loop release the manager.
func testDeletingTheCGMRetractsEveryAlertBeforeNotifying() {
func testDeletingTheCGMRetractsEveryAlertBeforeNotifying() async {
var state = G7CGMManagerState()
state.sensorID = "DXCM99"
state.activatedAt = Date()
state.lifecycleAlertsScheduledFor = "scheduled"
let manager = makeManager(state: state)

let done = expectation(description: "delete completed")
manager.delete { done.fulfill() }
wait(for: [done], timeout: 2)
settle()
await manager.delete()

XCTAssertEqual(recorder.events.last, "delete", "delete() returns once Loop has been told to delete")
XCTAssertEqual(
Set(recorder.retracted.map(\.alertIdentifier)),
Set(G7LifecycleAlert.allCases.map(\.rawValue)),
Expand All @@ -281,18 +279,15 @@ class G7LifecycleAlertManagerTests: XCTestCase {
}

/// A manager passed its configuration schedules the same alerts, so deleting it retracts them.
func testDeletingAPassedConfigurationRetractsEveryAlert() {
func testDeletingAPassedConfigurationRetractsEveryAlert() async {
var shared = G7CGMManagerState()
shared.sensorID = "DXCM99"
shared.activatedAt = Date()
var state = G7CGMManagerState.adopted(from: shared.rawValue)
state.lifecycleAlertsScheduledFor = "scheduled"
let manager = makeManager(state: state)

let done = expectation(description: "delete completed")
manager.delete { done.fulfill() }
wait(for: [done], timeout: 2)
settle()
await manager.delete()

XCTAssertEqual(Set(recorder.retracted.map(\.alertIdentifier)), Set(G7LifecycleAlert.allCases.map(\.rawValue)))
}
Expand Down
6 changes: 2 additions & 4 deletions G7SensorKitTests/G7SessionModeMigrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -484,13 +484,11 @@ final class G7SessionModeMigrationTests: XCTestCase {
XCTAssertGreaterThan(manager.state.pairedAt!, state.pairedAt!)
}

func testDeletingKeepsThePreviousSensorAsRemoved() {
func testDeletingKeepsThePreviousSensorAsRemoved() async {
var state = G7CGMManagerState(rawValue: legacyRawState)
state.sensorID = "DXCM99"
let manager = makeManager(state: state)
let done = expectation(description: "deleted")
manager.delete { done.fulfill() }
wait(for: [done], timeout: 2)
await manager.delete()
XCTAssertEqual(manager.state.previousSensor?.endReason, .deleted)
}

Expand Down
14 changes: 5 additions & 9 deletions G7SensorKitUI/G7CGMManager/G7UICoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,11 @@ class G7UICoordinator: UINavigationController, CGMManagerOnboarding, CompletionN
}
},
deleteCGM: { [ weak self] in
// `delete`, not `notifyDelegateOfDeletion`: the manager's
// own teardown retracts its standing alerts first, and Loop
// replays anything left behind at every launch.
self?.cgmManager?.delete {
DispatchQueue.main.async {
if let self = self {
self.completionDelegate?.completionNotifyingDidComplete(self)
self.dismiss(animated: true)
}
Task {
await self?.cgmManager?.delete()
if let self = self {
self.completionDelegate?.completionNotifyingDidComplete(self)
self.dismiss(animated: true)
}
}
},
Expand Down