Skip to content

Commit 735d7ad

Browse files
authored
Merge pull request #555 from tidepool-org/cameron/LOOP-4483-study-product-selection
[LOOP-4483] Study Product Selection
2 parents 13f1357 + c72714f commit 735d7ad

File tree

6 files changed

+98
-13
lines changed

6 files changed

+98
-13
lines changed

Loop/AppDelegate.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, WindowProvider {
4343

4444
func applicationWillEnterForeground(_ application: UIApplication) {
4545
log.default(#function)
46+
47+
loopAppManager.askUserToConfirmCrashIfNecessary()
4648
}
4749

4850
func applicationWillTerminate(_ application: UIApplication) {

Loop/Managers/Alerts/AlertManager.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,19 @@ extension AlertManager: AlertPermissionsCheckerDelegate {
777777
}
778778
}
779779

780+
extension AlertManager {
781+
func presentConfirmCrashAlert(confirmAction: @escaping () -> Void) {
782+
let alert = UIAlertController(title: "New Study Product Detected", message: "We've detected a new study product is selected. In order to show use this study product, Tidepool Loop will need to restart.", preferredStyle: .alert)
783+
alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: { _ in
784+
confirmAction()
785+
exit(0)
786+
}))
787+
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
788+
789+
alertPresenter.present(alert, animated: true)
790+
}
791+
}
792+
780793
fileprivate extension UserDefaults {
781794
private enum Key: String {
782795
case hasIssuedNotificationPermissionsAlert = "com.loopkit.Loop.HasIssuedNotificationPermissionsAlert"

Loop/Managers/DeviceDataManager.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,8 @@ final class DeviceDataManager {
442442
return pluginManager.availablePumpManagers + availableStaticPumpManagers
443443
}
444444

445-
func setupPumpManager(withIdentifier identifier: String, initialSettings settings: PumpManagerSetupSettings) -> Swift.Result<SetupUIResult<PumpManagerViewController, PumpManager>, Error> {
446-
switch setupPumpManagerUI(withIdentifier: identifier, initialSettings: settings) {
445+
func setupPumpManager(withIdentifier identifier: String, initialSettings settings: PumpManagerSetupSettings, prefersToSkipUserInteraction: Bool) -> Swift.Result<SetupUIResult<PumpManagerViewController, PumpManager>, Error> {
446+
switch setupPumpManagerUI(withIdentifier: identifier, initialSettings: settings, prefersToSkipUserInteraction: prefersToSkipUserInteraction) {
447447
case .failure(let error):
448448
return .failure(error)
449449
case .success(let success):
@@ -458,12 +458,12 @@ final class DeviceDataManager {
458458

459459
struct UnknownPumpManagerIdentifierError: Error {}
460460

461-
func setupPumpManagerUI(withIdentifier identifier: String, initialSettings settings: PumpManagerSetupSettings) -> Swift.Result<SetupUIResult<PumpManagerViewController, PumpManagerUI>, Error> {
461+
func setupPumpManagerUI(withIdentifier identifier: String, initialSettings settings: PumpManagerSetupSettings, prefersToSkipUserInteraction: Bool = false) -> Swift.Result<SetupUIResult<PumpManagerViewController, PumpManagerUI>, Error> {
462462
guard let pumpManagerUIType = pumpManagerTypeByIdentifier(identifier) else {
463463
return .failure(UnknownPumpManagerIdentifierError())
464464
}
465465

466-
let result = pumpManagerUIType.setupViewController(initialSettings: settings, bluetoothProvider: bluetoothProvider, colorPalette: .default, allowDebugFeatures: FeatureFlags.allowDebugFeatures, allowedInsulinTypes: allowedInsulinTypes)
466+
let result = pumpManagerUIType.setupViewController(initialSettings: settings, bluetoothProvider: bluetoothProvider, colorPalette: .default, allowDebugFeatures: FeatureFlags.allowDebugFeatures, prefersToSkipUserInteraction: prefersToSkipUserInteraction, allowedInsulinTypes: allowedInsulinTypes)
467467
if case .createdAndOnboarded(let pumpManagerUI) = result {
468468
pumpManagerOnboarding(didCreatePumpManager: pumpManagerUI)
469469
pumpManagerOnboarding(didOnboardPumpManager: pumpManagerUI)
@@ -546,12 +546,12 @@ final class DeviceDataManager {
546546
return availableCGMManagers
547547
}
548548

549-
func setupCGMManager(withIdentifier identifier: String) -> Swift.Result<SetupUIResult<CGMManagerViewController, CGMManager>, Error> {
549+
func setupCGMManager(withIdentifier identifier: String, prefersToSkipUserInteraction: Bool = false) -> Swift.Result<SetupUIResult<CGMManagerViewController, CGMManager>, Error> {
550550
if let cgmManager = setupCGMManagerFromPumpManager(withIdentifier: identifier) {
551551
return .success(.createdAndOnboarded(cgmManager))
552552
}
553553

554-
switch setupCGMManagerUI(withIdentifier: identifier) {
554+
switch setupCGMManagerUI(withIdentifier: identifier, prefersToSkipUserInteraction: prefersToSkipUserInteraction) {
555555
case .failure(let error):
556556
return .failure(error)
557557
case .success(let success):
@@ -566,12 +566,12 @@ final class DeviceDataManager {
566566

567567
struct UnknownCGMManagerIdentifierError: Error {}
568568

569-
fileprivate func setupCGMManagerUI(withIdentifier identifier: String) -> Swift.Result<SetupUIResult<CGMManagerViewController, CGMManagerUI>, Error> {
569+
fileprivate func setupCGMManagerUI(withIdentifier identifier: String, prefersToSkipUserInteraction: Bool) -> Swift.Result<SetupUIResult<CGMManagerViewController, CGMManagerUI>, Error> {
570570
guard let cgmManagerUIType = cgmManagerTypeByIdentifier(identifier) else {
571571
return .failure(UnknownCGMManagerIdentifierError())
572572
}
573573

574-
let result = cgmManagerUIType.setupViewController(bluetoothProvider: bluetoothProvider, displayGlucoseUnitObservable: displayGlucoseUnitObservable, colorPalette: .default, allowDebugFeatures: FeatureFlags.allowDebugFeatures)
574+
let result = cgmManagerUIType.setupViewController(bluetoothProvider: bluetoothProvider, displayGlucoseUnitObservable: displayGlucoseUnitObservable, colorPalette: .default, allowDebugFeatures: FeatureFlags.allowDebugFeatures, prefersToSkipUserInteraction: prefersToSkipUserInteraction)
575575
if case .createdAndOnboarded(let cgmManagerUI) = result {
576576
cgmManagerOnboarding(didCreateCGMManager: cgmManagerUI)
577577
cgmManagerOnboarding(didOnboardCGMManager: cgmManagerUI)

Loop/Managers/LoopAppManager.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Intents
1111
import Combine
1212
import LoopKit
1313
import LoopKitUI
14+
import TidepoolKit
1415
import MockKit
1516
import HealthKit
1617
import WidgetKit
@@ -368,6 +369,49 @@ class LoopAppManager: NSObject {
368369
}
369370
return false
370371
}
372+
373+
private func resetLoop() {
374+
deviceDataManager.pumpManager?.prepareForDeactivation({ _ in })
375+
376+
resetLoopUserDefaults()
377+
resetLoopDocuments()
378+
}
379+
380+
private func resetLoopUserDefaults() {
381+
// Store values to persist
382+
let allowDebugFeatures = UserDefaults.appGroup?.allowDebugFeatures
383+
let studyProductSelection = UserDefaults.appGroup?.studyProductSelection
384+
385+
// Wipe away whole domain
386+
UserDefaults.appGroup?.removePersistentDomain(forName: Bundle.main.appGroupSuiteName)
387+
388+
// Restore values to persist
389+
UserDefaults.appGroup?.allowDebugFeatures = allowDebugFeatures ?? false
390+
UserDefaults.appGroup?.studyProductSelection = studyProductSelection
391+
}
392+
393+
private func resetLoopDocuments() {
394+
let appGroup = Bundle.main.appGroupSuiteName
395+
guard let directoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else {
396+
preconditionFailure("Could not get a container directory URL. Please ensure App Groups are set up correctly in entitlements.")
397+
}
398+
399+
let documents: URL = directoryURL.appendingPathComponent("com.loopkit.LoopKit", isDirectory: true)
400+
try? FileManager.default.removeItem(at: documents)
401+
402+
guard let localDocuments = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
403+
preconditionFailure("Could not get a documents directory URL.")
404+
}
405+
try? FileManager.default.removeItem(at: localDocuments)
406+
}
407+
408+
func askUserToConfirmCrashIfNecessary() {
409+
if UserDefaults.appGroup?.resetLoop == true {
410+
alertManager.presentConfirmCrashAlert() { [weak self] in
411+
self?.resetLoop()
412+
}
413+
}
414+
}
371415

372416
private var rootViewController: UIViewController? {
373417
get { windowProvider?.window?.rootViewController }

Loop/Managers/OnboardingManager.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,9 @@ extension OnboardingManager: CGMManagerProvider {
340340
return cgmManagerType.onboardingImage
341341
}
342342

343-
func onboardCGMManager(withIdentifier identifier: String) -> Swift.Result<OnboardingResult<CGMManagerViewController, CGMManager>, Error> {
343+
func onboardCGMManager(withIdentifier identifier: String, prefersToSkipUserInteraction: Bool) -> Swift.Result<OnboardingResult<CGMManagerViewController, CGMManager>, Error> {
344344
guard let cgmManager = deviceDataManager.cgmManager else {
345-
return deviceDataManager.setupCGMManager(withIdentifier: identifier)
345+
return deviceDataManager.setupCGMManager(withIdentifier: identifier, prefersToSkipUserInteraction: prefersToSkipUserInteraction)
346346
}
347347
guard cgmManager.managerIdentifier == identifier else {
348348
return .failure(OnboardingError.invalidState)
@@ -384,9 +384,9 @@ extension OnboardingManager: PumpManagerProvider {
384384
maximumBasalScheduleEntryCount: pumpManagerType.onboardingMaximumBasalScheduleEntryCount)
385385
}
386386

387-
func onboardPumpManager(withIdentifier identifier: String, initialSettings settings: PumpManagerSetupSettings) -> Swift.Result<OnboardingResult<PumpManagerViewController, PumpManager>, Error> {
387+
func onboardPumpManager(withIdentifier identifier: String, initialSettings settings: PumpManagerSetupSettings, prefersToSkipUserInteraction: Bool) -> Swift.Result<OnboardingResult<PumpManagerViewController, PumpManager>, Error> {
388388
guard let pumpManager = deviceDataManager.pumpManager else {
389-
return deviceDataManager.setupPumpManager(withIdentifier: identifier, initialSettings: settings)
389+
return deviceDataManager.setupPumpManager(withIdentifier: identifier, initialSettings: settings, prefersToSkipUserInteraction: prefersToSkipUserInteraction)
390390
}
391391
guard pumpManager.managerIdentifier == identifier else {
392392
return .failure(OnboardingError.invalidState)
@@ -435,6 +435,7 @@ extension OnboardingManager: TherapySettingsProvider {
435435

436436
extension OnboardingManager: OnboardingProvider {
437437
var allowDebugFeatures: Bool { FeatureFlags.allowDebugFeatures } // NOTE: DEBUG FEATURES - DEBUG AND TEST ONLY
438+
var studyProductSelection: StudyProduct { StudyProduct(rawValue: UserDefaults.appGroup?.studyProductSelection ?? "none") ?? .none }
438439
}
439440

440441
// MARK: - OnboardingUI

LoopCore/NSUserDefaults.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ extension UserDefaults {
2020
case lastProfileExpirationAlertDate = "com.loopkit.Loop.lastProfileExpirationAlertDate"
2121
case allowDebugFeatures = "com.loopkit.Loop.allowDebugFeatures"
2222
case allowSimulators = "com.loopkit.Loop.allowSimulators"
23+
case studyProductSelection = "com.loopkit.Loop.studyProductSelection"
24+
case resetLoop = "com.loopkit.Loop.resetLoop"
2325
}
2426

2527
public static let appGroup = UserDefaults(suiteName: Bundle.main.appGroupSuiteName)
@@ -115,12 +117,35 @@ extension UserDefaults {
115117
}
116118

117119
public var allowDebugFeatures: Bool {
118-
return bool(forKey: Key.allowDebugFeatures.rawValue)
120+
get {
121+
bool(forKey: Key.allowDebugFeatures.rawValue)
122+
}
123+
set {
124+
set(newValue, forKey: Key.allowDebugFeatures.rawValue)
125+
}
119126
}
120127

121128
public var allowSimulators: Bool {
122129
return bool(forKey: Key.allowSimulators.rawValue)
123130
}
131+
132+
public var studyProductSelection: String? {
133+
get {
134+
string(forKey: Key.studyProductSelection.rawValue)
135+
}
136+
set {
137+
set(newValue, forKey: Key.studyProductSelection.rawValue)
138+
}
139+
}
140+
141+
public var resetLoop: Bool {
142+
get {
143+
bool(forKey: Key.resetLoop.rawValue)
144+
}
145+
set {
146+
setValue(newValue, forKey: Key.resetLoop.rawValue)
147+
}
148+
}
124149

125150
public func removeLegacyLoopSettings() {
126151
removeObject(forKey: "com.loudnate.Naterade.BasalRateSchedule")

0 commit comments

Comments
 (0)