Skip to content

Commit 867f825

Browse files
authored
[LOOP-4648] Smarter Loop Reset State
2 parents 8e1f29e + bf2f897 commit 867f825

File tree

3 files changed

+162
-67
lines changed

3 files changed

+162
-67
lines changed

Loop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@
270270
7D7076591FE06EE2004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70765B1FE06EE2004AC8EA /* Localizable.strings */; };
271271
7D70765E1FE06EE3004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076601FE06EE3004AC8EA /* Localizable.strings */; };
272272
7D7076631FE06EE4004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076651FE06EE4004AC8EA /* Localizable.strings */; };
273+
7E69CFFC2A16A77E00203CBD /* ResetLoopManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */; };
273274
891B508524342BE1005DA578 /* CarbAndBolusFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 891B508424342BE1005DA578 /* CarbAndBolusFlowViewModel.swift */; };
274275
892A5D59222F0A27008961AB /* Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A5D58222F0A27008961AB /* Debug.swift */; };
275276
892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */; };
@@ -1254,6 +1255,7 @@
12541255
7DD382771F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = "<group>"; };
12551256
7DD382781F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainInterface.strings; sourceTree = "<group>"; };
12561257
7DD382791F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Interface.strings; sourceTree = "<group>"; };
1258+
7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetLoopManager.swift; sourceTree = "<group>"; };
12571259
80F864E52433BF5D0026EC26 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = "<group>"; };
12581260
891B508424342BE1005DA578 /* CarbAndBolusFlowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbAndBolusFlowViewModel.swift; sourceTree = "<group>"; };
12591261
892A5D29222EF60A008961AB /* MockKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = MockKit.framework; path = Carthage/Build/iOS/MockKit.framework; sourceTree = SOURCE_ROOT; };
@@ -2440,6 +2442,7 @@
24402442
E9B355232935906B0076AB04 /* Missed Meal Detection */,
24412443
C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */,
24422444
A96DAC2B2838F31200D94E38 /* SharedLogging.swift */,
2445+
7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */,
24432446
);
24442447
path = Managers;
24452448
sourceTree = "<group>";
@@ -3982,6 +3985,7 @@
39823985
C1C660D1252E4DD5009B5C32 /* LoopConstants.swift in Sources */,
39833986
432E73CB1D24B3D6009AD15D /* RemoteDataServicesManager.swift in Sources */,
39843987
C18913B52524F24C007B0683 /* DeviceDataManager+SimpleBolusViewModelDelegate.swift in Sources */,
3988+
7E69CFFC2A16A77E00203CBD /* ResetLoopManager.swift in Sources */,
39853989
B40D07C7251A89D500C1C6D7 /* GlucoseDisplay.swift in Sources */,
39863990
43C2FAE11EB656A500364AFF /* GlucoseEffectVelocity.swift in Sources */,
39873991
);

Loop/Managers/LoopAppManager.swift

Lines changed: 38 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class LoopAppManager: NSObject {
8181
private var settingsManager: SettingsManager!
8282
private var loggingServicesManager = LoggingServicesManager()
8383
private var analyticsServicesManager = AnalyticsServicesManager()
84+
private var resetLoopManager: ResetLoopManager!
8485

8586
private var overrideHistory = UserDefaults.appGroup?.overrideHistory ?? TemporaryScheduleOverrideHistory.init()
8687

@@ -142,6 +143,8 @@ class LoopAppManager: NSObject {
142143
if state == .launchHomeScreen {
143144
launchHomeScreen()
144145
}
146+
147+
askUserToConfirmLoopReset()
145148
}
146149

147150
private func checkProtectedDataAvailable() {
@@ -164,6 +167,8 @@ class LoopAppManager: NSObject {
164167
OrientationLock.deviceOrientationController = self
165168
UNUserNotificationCenter.current().delegate = self
166169

170+
resetLoopManager = ResetLoopManager(delegate: self)
171+
167172
let localCacheDuration = Bundle.main.localCacheDuration
168173
let cacheStore = PersistenceController.controllerInAppGroupDirectory()
169174

@@ -289,8 +294,6 @@ class LoopAppManager: NSObject {
289294
self.state = state.next
290295

291296
alertManager.playbackAlertsFromPersistence()
292-
293-
askUserToConfirmLoopReset()
294297
}
295298

296299
// MARK: - Life Cycle
@@ -399,70 +402,6 @@ class LoopAppManager: NSObject {
399402
}
400403
return false
401404
}
402-
403-
func askUserToConfirmLoopReset() {
404-
if UserDefaults.appGroup?.userRequestedLoopReset == true {
405-
alertManager.presentLoopResetConfirmationAlert(
406-
confirmAction: { [weak self] completion in
407-
guard let pumpManager = self?.deviceDataManager.pumpManager else {
408-
self?.resetLoop()
409-
completion()
410-
return
411-
}
412-
413-
pumpManager.prepareForDeactivation() { [weak self] error in
414-
guard let error = error else {
415-
self?.resetLoop()
416-
completion()
417-
return
418-
}
419-
self?.alertManager.presentCouldNotResetLoopAlert(error: error)
420-
}
421-
},
422-
cancelAction: {
423-
UserDefaults.appGroup?.userRequestedLoopReset = false
424-
}
425-
)
426-
}
427-
}
428-
429-
private func resetLoop() {
430-
deviceDataManager.pluginManager.availableSupports.forEach { supportUI in
431-
supportUI.loopWillReset()
432-
}
433-
434-
resetLoopDocuments()
435-
resetLoopUserDefaults()
436-
437-
deviceDataManager.pluginManager.availableSupports.forEach { supportUI in
438-
supportUI.loopDidReset()
439-
}
440-
}
441-
442-
private func resetLoopUserDefaults() {
443-
// Store values to persist
444-
let allowDebugFeatures = UserDefaults.appGroup?.allowDebugFeatures
445-
446-
// Wipe away whole domain
447-
UserDefaults.appGroup?.removePersistentDomain(forName: Bundle.main.appGroupSuiteName)
448-
449-
// Restore values to persist
450-
UserDefaults.appGroup?.allowDebugFeatures = allowDebugFeatures ?? false
451-
}
452-
453-
private func resetLoopDocuments() {
454-
guard let directoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Bundle.main.appGroupSuiteName) else {
455-
preconditionFailure("Could not get a container directory URL. Please ensure App Groups are set up correctly in entitlements.")
456-
}
457-
458-
let documents: URL = directoryURL.appendingPathComponent("com.loopkit.LoopKit", isDirectory: true)
459-
try? FileManager.default.removeItem(at: documents)
460-
461-
guard let localDocuments = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
462-
preconditionFailure("Could not get a documents directory URL.")
463-
}
464-
try? FileManager.default.removeItem(at: localDocuments)
465-
}
466405

467406
private var rootViewController: UIViewController? {
468407
get { windowProvider?.window?.rootViewController }
@@ -474,7 +413,9 @@ class LoopAppManager: NSObject {
474413

475414
extension LoopAppManager: AlertPresenter {
476415
func present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?) {
477-
rootViewController?.topmostViewController.present(viewControllerToPresent, animated: animated, completion: completion)
416+
DispatchQueue.main.async {
417+
self.rootViewController?.topmostViewController.present(viewControllerToPresent, animated: animated, completion: completion)
418+
}
478419
}
479420

480421
func dismissTopMost(animated: Bool, completion: (() -> Void)?) {
@@ -625,3 +566,33 @@ extension LoopAppManager: TemporaryScheduleOverrideHistoryDelegate {
625566
}
626567
}
627568

569+
extension LoopAppManager: ResetLoopManagerDelegate {
570+
func askUserToConfirmLoopReset() {
571+
resetLoopManager.askUserToConfirmLoopReset()
572+
}
573+
574+
func presentConfirmationAlert(confirmAction: @escaping (PumpManager?, @escaping () -> Void) -> Void, cancelAction: @escaping () -> Void) {
575+
alertManager.presentLoopResetConfirmationAlert(
576+
confirmAction: { [weak self] completion in
577+
confirmAction(self?.deviceDataManager.pumpManager, completion)
578+
},
579+
cancelAction: cancelAction
580+
)
581+
}
582+
583+
func loopWillReset() {
584+
deviceDataManager.pluginManager.availableSupports.forEach { supportUI in
585+
supportUI.loopWillReset()
586+
}
587+
}
588+
589+
func loopDidReset() {
590+
deviceDataManager.pluginManager.availableSupports.forEach { supportUI in
591+
supportUI.loopDidReset()
592+
}
593+
}
594+
595+
func presentCouldNotResetLoopAlert(error: Error) {
596+
alertManager.presentCouldNotResetLoopAlert(error: error)
597+
}
598+
}

Loop/Managers/ResetLoopManager.swift

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//
2+
// ResetLoopManager.swift
3+
// Loop
4+
//
5+
// Created by Cameron Ingham on 5/18/23.
6+
// Copyright © 2023 LoopKit Authors. All rights reserved.
7+
//
8+
9+
import LoopKit
10+
11+
protocol ResetLoopManagerDelegate: AnyObject {
12+
func loopWillReset()
13+
func loopDidReset()
14+
15+
func presentConfirmationAlert(
16+
confirmAction: @escaping (_ pumpManager: PumpManager?, _ completion: @escaping () -> Void) -> Void,
17+
cancelAction: @escaping () -> Void
18+
)
19+
20+
func presentCouldNotResetLoopAlert(error: Error)
21+
}
22+
23+
class ResetLoopManager {
24+
25+
private weak var delegate: ResetLoopManagerDelegate?
26+
27+
private var loopIsAlreadyReset: Bool = false
28+
private var resetAlertPresented: Bool = false
29+
30+
init(delegate: ResetLoopManagerDelegate?) {
31+
self.delegate = delegate
32+
33+
checkIfLoopIsAlreadyReset()
34+
}
35+
36+
func askUserToConfirmLoopReset() {
37+
if loopIsAlreadyReset {
38+
UserDefaults.appGroup?.userRequestedLoopReset = false
39+
}
40+
41+
if UserDefaults.appGroup?.userRequestedLoopReset == true && !resetAlertPresented {
42+
resetAlertPresented = true
43+
44+
delegate?.presentConfirmationAlert(
45+
confirmAction: { [weak self] pumpManager, completion in
46+
self?.resetAlertPresented = false
47+
48+
guard let pumpManager else {
49+
self?.resetLoop()
50+
completion()
51+
return
52+
}
53+
54+
pumpManager.prepareForDeactivation() { [weak self] error in
55+
guard let error = error else {
56+
self?.resetLoop()
57+
completion()
58+
return
59+
}
60+
61+
self?.delegate?.presentCouldNotResetLoopAlert(error: error)
62+
}
63+
}, cancelAction: { [weak self] in
64+
self?.resetAlertPresented = false
65+
UserDefaults.appGroup?.userRequestedLoopReset = false
66+
}
67+
)
68+
}
69+
70+
checkIfLoopIsAlreadyReset()
71+
}
72+
73+
private func checkIfLoopIsAlreadyReset() {
74+
let fileManager = FileManager.default
75+
76+
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
77+
return
78+
}
79+
80+
guard let hasReset = try? fileManager.contentsOfDirectory(atPath: url.path).count <= 1 else {
81+
return
82+
}
83+
84+
loopIsAlreadyReset = hasReset
85+
}
86+
87+
private func resetLoop() {
88+
delegate?.loopWillReset()
89+
90+
resetLoopDocuments()
91+
resetLoopUserDefaults()
92+
93+
delegate?.loopDidReset()
94+
}
95+
96+
private func resetLoopUserDefaults() {
97+
// Store values to persist
98+
let allowDebugFeatures = UserDefaults.appGroup?.allowDebugFeatures
99+
100+
// Wipe away whole domain
101+
UserDefaults.appGroup?.removePersistentDomain(forName: Bundle.main.appGroupSuiteName)
102+
103+
// Restore values to persist
104+
UserDefaults.appGroup?.allowDebugFeatures = allowDebugFeatures ?? false
105+
}
106+
107+
private func resetLoopDocuments() {
108+
guard let directoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Bundle.main.appGroupSuiteName) else {
109+
preconditionFailure("Could not get a container directory URL. Please ensure App Groups are set up correctly in entitlements.")
110+
}
111+
112+
let documents: URL = directoryURL.appendingPathComponent("com.loopkit.LoopKit", isDirectory: true)
113+
try? FileManager.default.removeItem(at: documents)
114+
115+
guard let localDocuments = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
116+
preconditionFailure("Could not get a documents directory URL.")
117+
}
118+
try? FileManager.default.removeItem(at: localDocuments)
119+
}
120+
}

0 commit comments

Comments
 (0)