From cb8ab6861d89ea79cae87b90117534199ae4991c Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 7 Feb 2024 15:36:13 -0800 Subject: [PATCH 1/8] Add FirstAppear --- OmniKit.xcodeproj/project.pbxproj | 4 ++++ OmniKitUI/Views/FirstAppear.swift | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 OmniKitUI/Views/FirstAppear.swift diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index d3293ea..16ff9a4 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -192,6 +192,7 @@ D803399A2A500D3D004FF953 /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12401B229C7D8E900B32844 /* CRC8.swift */; }; D803399B2A50122F004FF953 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12401B329C7D8E900B32844 /* Packet.swift */; }; D803399C2A50128D004FF953 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0129C7DD4700435701 /* LocalizedString.swift */; }; + D8D9275E2B744A6C00D154B2 /* FirstAppear.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -435,6 +436,7 @@ C12EDA1529C7DFF100435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA1729C7E01800435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA1A29C7E06900435701 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; + D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstAppear.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -712,6 +714,7 @@ C124022D29C7DA9700B32844 /* Views */ = { isa = PBXGroup; children = ( + D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */, C124022F29C7DA9700B32844 /* CheckInsertedCannulaView.swift */, C124023129C7DA9700B32844 /* DesignElements */, C124023529C7DA9700B32844 /* LowReservoirReminderEditView.swift */, @@ -1214,6 +1217,7 @@ C124026E29C7DA9700B32844 /* FrameworkLocalText.swift in Sources */, C124028F29C7DA9700B32844 /* PodDetailsView.swift in Sources */, C124028629C7DA9700B32844 /* NotificationSettingsView.swift in Sources */, + D8D9275E2B744A6C00D154B2 /* FirstAppear.swift in Sources */, C124027D29C7DA9700B32844 /* InsertCannulaView.swift in Sources */, C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */, C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */, diff --git a/OmniKitUI/Views/FirstAppear.swift b/OmniKitUI/Views/FirstAppear.swift new file mode 100644 index 0000000..25f49bb --- /dev/null +++ b/OmniKitUI/Views/FirstAppear.swift @@ -0,0 +1,30 @@ +// +// FirstAppear.swift +// OmniKit +// +// Created by Joe Moran on 9/24/23. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import SwiftUI + +extension View { + func onFirstAppear(_ action: @escaping () -> ()) -> some View { + modifier(FirstAppear(action: action)) + } +} + +private struct FirstAppear: ViewModifier { + let action: () -> () + + // State used to insure action is invoked here only once + @State private var hasAppeared = false + + func body(content: Content) -> some View { + content.onAppear { + guard !hasAppeared else { return } + hasAppeared = true + action() + } + } +} From db001264328d9b2b8d746a5bbdfa5ac9f0ff7ad8 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 7 Feb 2024 15:51:46 -0800 Subject: [PATCH 2/8] Alphabetize OmniKitUI/Views files --- OmniKit.xcodeproj/project.pbxproj | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index 16ff9a4..9ba1ad6 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -714,32 +714,32 @@ C124022D29C7DA9700B32844 /* Views */ = { isa = PBXGroup; children = ( - D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */, + C124024629C7DA9700B32844 /* AttachPodView.swift */, + C124024C29C7DA9700B32844 /* BasalStateView.swift */, + C124024429C7DA9700B32844 /* BeepPreferenceSelectionView.swift */, C124022F29C7DA9700B32844 /* CheckInsertedCannulaView.swift */, + C124024B29C7DA9700B32844 /* DeactivatePodView.swift */, + C124024A29C7DA9700B32844 /* DeliveryUncertaintyRecoveryView.swift */, C124023129C7DA9700B32844 /* DesignElements */, - C124023529C7DA9700B32844 /* LowReservoirReminderEditView.swift */, - C124023629C7DA9700B32844 /* InsertCannulaView.swift */, - C124023729C7DA9700B32844 /* RileyLinkSetupView.swift */, - C124023829C7DA9700B32844 /* SetupCompleteView.swift */, C124023A29C7DA9700B32844 /* ExpirationReminderPickerView.swift */, - C124023B29C7DA9700B32844 /* UncertaintyRecoveredView.swift */, + C124024529C7DA9700B32844 /* ExpirationReminderSetupView.swift */, + D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */, + C124023629C7DA9700B32844 /* InsertCannulaView.swift */, C124023C29C7DA9700B32844 /* InsulinTypeConfirmation.swift */, + C124023529C7DA9700B32844 /* LowReservoirReminderEditView.swift */, + C124024229C7DA9700B32844 /* LowReservoirReminderSetupView.swift */, C124023D29C7DA9700B32844 /* ManualTempBasalEntryView.swift */, C124023F29C7DA9700B32844 /* NotificationSettingsView.swift */, + C124024929C7DA9700B32844 /* OmnipodReservoirView.swift */, C124024029C7DA9700B32844 /* OmnipodSettingsView.swift */, - C124024129C7DA9700B32844 /* PodSetupView.swift */, - C124024229C7DA9700B32844 /* LowReservoirReminderSetupView.swift */, C124024329C7DA9700B32844 /* PairPodView.swift */, - C124024429C7DA9700B32844 /* BeepPreferenceSelectionView.swift */, - C124024529C7DA9700B32844 /* ExpirationReminderSetupView.swift */, - C124024629C7DA9700B32844 /* AttachPodView.swift */, - C124024729C7DA9700B32844 /* ScheduledExpirationReminderEditView.swift */, C124024829C7DA9700B32844 /* PodDetailsView.swift */, - C124024929C7DA9700B32844 /* OmnipodReservoirView.swift */, - C124024A29C7DA9700B32844 /* DeliveryUncertaintyRecoveryView.swift */, - C124024B29C7DA9700B32844 /* DeactivatePodView.swift */, - C124024C29C7DA9700B32844 /* BasalStateView.swift */, + C124024129C7DA9700B32844 /* PodSetupView.swift */, + C124023729C7DA9700B32844 /* RileyLinkSetupView.swift */, + C124024729C7DA9700B32844 /* ScheduledExpirationReminderEditView.swift */, + C124023829C7DA9700B32844 /* SetupCompleteView.swift */, C124024D29C7DA9700B32844 /* TimeView.swift */, + C124023B29C7DA9700B32844 /* UncertaintyRecoveredView.swift */, ); path = Views; sourceTree = ""; From 6c3311995a0b78b15454d244c4890714ed819ebd Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 7 Feb 2024 16:16:31 -0800 Subject: [PATCH 3/8] Add cannulaInsertionSuccessfullyStarted var to podState & pumpManager --- OmniKit/PumpManager/OmnipodPumpManager.swift | 4 ++++ OmniKit/PumpManager/PodState.swift | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index ec97083..f1852ec 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -589,6 +589,10 @@ extension OmnipodPumpManager { return .deactivating } + public var cannulaInsertionSuccessfullyStarted: Bool { + return state.podState?.setupProgress.cannulaInsertionSuccessfullyStarted == true + } + public var podCommState: PodCommState { return podCommState(for: state) } diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index fd051b6..954f055 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -41,6 +41,10 @@ public enum SetupProgress: Int { public var needsCannulaInsertion: Bool { return self.rawValue < SetupProgress.completed.rawValue } + + public var cannulaInsertionSuccessfullyStarted: Bool { + return self.rawValue > SetupProgress.startingInsertCannula.rawValue + } } // TODO: Mutating functions aren't guaranteed to synchronize read/write calls. From 68871e9e4f7bd071cddc96b837321f35503033ab Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 7 Feb 2024 16:37:34 -0800 Subject: [PATCH 4/8] For an in-progress insert cannula compute a wait time on remaining bolus --- OmniKit/PumpManager/PodCommsSession.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index c509ea5..ada4404 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -435,7 +435,8 @@ public class PodCommsSession { if status.podProgressStatus == .insertingCannula { podState.setupProgress = .cannulaInserting podState.updateFromStatusResponse(status, at: currentDate) - return insertionWait // Not sure when it started, wait full time to be sure + // return a non-zero wait time based on the bolus not yet delivered + return (status.bolusNotDelivered / Pod.primeDeliveryRate) + 1 } if status.podProgressStatus.readyForDelivery { markSetupProgressCompleted(statusResponse: status) From e1d66f13c22bbd3c8c6efa5ef25d45a5155514c5 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 7 Feb 2024 17:46:03 -0800 Subject: [PATCH 5/8] Call new resetPerPodPumpManagerState() func when starting a pod Reset podAttachmentConfirmed state variable for each new pod Reset all per pod PumpManagerAlerts for each new pod Update PumpStatusHighlight to "Finish Setup" while activating a paired pod Add comment noting that podCommState.deactivating value is never returned Add some missing state initializion for simulator in jumpStartPod() --- OmniKit/PumpManager/OmnipodPumpManager.swift | 32 ++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index f1852ec..98c8256 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -334,7 +334,7 @@ extension OmnipodPumpManager { switch podCommState(for: state) { case .activating: return PumpStatusHighlight( - localizedMessage: LocalizedString("Finish Pairing", comment: "Status highlight that when pod is activating."), + localizedMessage: LocalizedString("Finish Setup", comment: "Status highlight that when pod is activating."), imageName: "exclamationmark.circle.fill", state: .warning) case .deactivating: @@ -586,7 +586,7 @@ extension OmnipodPumpManager { } else if !podState.isSetupComplete { return .activating } - return .deactivating + return .deactivating // Can't be reached and thus will never be returned } public var cannulaInsertionSuccessfullyStarted: Bool { @@ -702,6 +702,26 @@ extension OmnipodPumpManager { } } + // Reset any PumpManager state that is really per pod state + private func resetPerPodPumpManagerState() { + setState({ (state) in + state.podAttachmentConfirmed = false + + // Remove any per pod triggeringSlot based pump manager alerts + // that don't span pods (i.e., all but timeOffsetChangeDetected). + for alert in state.activeAlerts { + if alert != .timeOffsetChangeDetected { + state.activeAlerts.remove(alert) + } + } + for alert in state.alertsWithPendingAcknowledgment { + if alert != .timeOffsetChangeDetected { + state.alertsWithPendingAcknowledgment.remove(alert) + } + } + }) + } + // MARK: Testing #if targetEnvironment(simulator) private func jumpStartPod(address: UInt32, lot: UInt32, tid: UInt32, fault: DetailedStatus? = nil, startDate: Date? = nil, mockFault: Bool) { @@ -716,8 +736,14 @@ extension OmnipodPumpManager { podComms = PodComms(podState: podState) + self.podComms.delegate = self + self.podComms.messageLogger = self + + self.resetPerPodPumpManagerState() + setState({ (state) in state.updatePodStateFromPodComms(podState) + state.scheduledExpirationReminderOffset = state.defaultExpirationReminderOffset }) } #endif @@ -812,6 +838,8 @@ extension OmnipodPumpManager { } } + self.resetPerPodPumpManagerState() + // Calls completion primeSession(result) } From 429740395c6ee6fc0ef5316ea47c2430f70d948b Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Thu, 8 Feb 2024 14:18:23 -0800 Subject: [PATCH 6/8] UI fixes and improvements for various pod setup restart situations + Rename func & enum names from pair to pairAndPrime for clarity + Start with pairAndPrime state when prime has not finalized + Gracefully handle restarts when prime or insert cannula is in progress + Add restart support to PairPodViewModel & View to skip button wait + Add restart support to InsertCannulaViewModel & View to skip button wait + Fix checkCannulaInsertionFinished() to use checkingInsertion state + Fix PodPairer.podIsActivated to properly handle a paired pod + Add new PodPairer.getPodCommState var to implement podIsActivated + Add missing LocalizedString for "Insert Cannula" navigation bar title --- .../OmnipodUICoordinator.swift | 28 +++++++---- .../ViewModels/InsertCannulaViewModel.swift | 37 ++++++++++----- OmniKitUI/ViewModels/PairPodViewModel.swift | 46 +++++++++++++------ OmniKitUI/ViewModels/PodLifeState.swift | 2 +- OmniKitUI/Views/InsertCannulaView.swift | 6 ++- OmniKitUI/Views/PairPodView.swift | 4 ++ 6 files changed, 87 insertions(+), 36 deletions(-) diff --git a/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift b/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift index 8865497..b188fd0 100644 --- a/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift +++ b/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift @@ -24,7 +24,7 @@ enum OmnipodUIScreen { case lowReservoirReminderSetup case insulinTypeSelection case rileyLinkSetup - case pairPod + case pairAndPrime case insertCannula case confirmAttachment case checkInsertedCannula @@ -45,8 +45,8 @@ enum OmnipodUIScreen { case .insulinTypeSelection: return .rileyLinkSetup case .rileyLinkSetup: - return .pairPod - case .pairPod: + return .pairAndPrime + case .pairAndPrime: return .confirmAttachment case .confirmAttachment: return .insertCannula @@ -61,7 +61,7 @@ enum OmnipodUIScreen { case .uncertaintyRecovered: return nil case .deactivate: - return .pairPod + return .pairAndPrime case .settings: return nil } @@ -199,7 +199,7 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl let view = OmnipodSettingsView(viewModel: viewModel, rileyLinkListDataSource: rileyLinkListDataSource, handleRileyLinkSelection: handleRileyLinkSelection, supportedInsulinTypes: allowedInsulinTypes) return hostingController(rootView: view) - case .pairPod: + case .pairAndPrime: pumpManagerOnboardingDelegate?.pumpManagerOnboarding(didCreatePumpManager: pumpManager) let viewModel = PairPodViewModel(podPairer: pumpManager) @@ -214,6 +214,11 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl self?.navigateTo(.deactivate) } + if pumpManager.podCommState == .activating { + // Looks we are restarting, use an alternate restarting state to immediately continue + viewModel.state = .restarting + } + let view = hostingController(rootView: PairPodView(viewModel: viewModel).onAppear(perform: {UIApplication.shared.isIdleTimerDisabled = true}), onDisappear: {UIApplication.shared.isIdleTimerDisabled = false}) view.navigationItem.title = LocalizedString("Pair Pod", comment: "Title for pod pairing screen") view.navigationItem.backButtonDisplayMode = .generic @@ -243,6 +248,11 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl self?.navigateTo(.deactivate) } + if pumpManager.cannulaInsertionSuccessfullyStarted { + // Looks we are restarting, use an alternate restarting state to immediately continue + viewModel.state = .restarting + } + let view = hostingController(rootView: InsertCannulaView(viewModel: viewModel).onAppear(perform: {UIApplication.shared.isIdleTimerDisabled = true}), onDisappear: {UIApplication.shared.isIdleTimerDisabled = false}) view.navigationItem.title = LocalizedString("Insert Cannula", comment: "Title for insert cannula screen") view.navigationItem.hidesBackButton = true @@ -344,8 +354,8 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl } private func hostingController(rootView: Content, onDisappear: @escaping () -> Void = {}) -> DismissibleHostingController { - return DismissibleHostingController(content: rootView, onDisappear: onDisappear, colorPalette: colorPalette) - } + return DismissibleHostingController(content: rootView, onDisappear: onDisappear, colorPalette: colorPalette) + } private func stepFinished() { if let nextStep = currentScreen.next() { @@ -411,13 +421,13 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl if pumpManager.podAttachmentConfirmed { return .insertCannula } else { - return .confirmAttachment + return .pairAndPrime // need to finish the priming } } else if !pumpManager.isOnboarded { if !pumpManager.initialConfigurationCompleted { return .firstRunScreen } - return .pairPod + return .pairAndPrime // pair and prime a new pod } else { return .settings } diff --git a/OmniKitUI/ViewModels/InsertCannulaViewModel.swift b/OmniKitUI/ViewModels/InsertCannulaViewModel.swift index 029ef7d..564e44c 100644 --- a/OmniKitUI/ViewModels/InsertCannulaViewModel.swift +++ b/OmniKitUI/ViewModels/InsertCannulaViewModel.swift @@ -15,7 +15,7 @@ public protocol CannulaInserter { func checkCannulaInsertionFinished(completion: @escaping (OmnipodPumpManagerError?) -> Void) } -extension OmnipodPumpManager: CannulaInserter {} +extension OmnipodPumpManager: CannulaInserter { } class InsertCannulaViewModel: ObservableObject, Identifiable { @@ -26,12 +26,13 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { case checkingInsertion case error(OmnipodPumpManagerError) case finished + case restarting var actionButtonAccessibilityLabel: String { switch self { case .ready, .startingInsertion: return LocalizedString("Slide Button to insert Cannula", comment: "Insert cannula slider button accessibility label while ready to pair") - case .inserting: + case .inserting, .restarting: return LocalizedString("Inserting. Please wait.", comment: "Insert cannula action button accessibility label while pairing") case .checkingInsertion: return LocalizedString("Checking Insertion", comment: "Insert cannula action button accessibility label checking insertion") @@ -57,7 +58,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { return LocalizedString("Slide to Insert Cannula", comment: "Cannula insertion button text while ready to insert") case .error: return LocalizedString("Retry", comment: "Cannula insertion button text while showing error") - case .inserting, .startingInsertion: + case .inserting, .startingInsertion, .restarting: return LocalizedString("Inserting...", comment: "Cannula insertion button text while inserting") case .checkingInsertion: return LocalizedString("Checking...", comment: "Cannula insertion button text while checking insertion") @@ -82,7 +83,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { switch self { case .ready, .error: return .hidden - case .startingInsertion, .checkingInsertion: + case .startingInsertion, .checkingInsertion, .restarting: return .indeterminantProgress case .inserting(let finishTime): return .timedProgress(finishTime: finishTime) @@ -102,7 +103,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { var isProcessing: Bool { switch self { - case .startingInsertion, .inserting: + case .startingInsertion, .inserting, .restarting: return true default: return false @@ -134,6 +135,13 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { } } + var restarting: Bool { + if case .restarting = self.state { + return true + } + return false + } + var didFinish: (() -> Void)? var didRequestDeactivation: (() -> Void)? @@ -157,7 +165,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { // } private func checkCannulaInsertionFinished() { - state = .startingInsertion + state = .checkingInsertion cannulaInserter.checkCannulaInsertionFinished() { (error) in DispatchQueue.main.async { if let error = error { @@ -169,9 +177,8 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { } } - private func insertCannula() { - state = .startingInsertion - + private func insertCannula(initState: InsertCannulaViewModelState) { + state = initState cannulaInserter.insertCannula { (result) in DispatchQueue.main.async { switch(result) { @@ -206,15 +213,21 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { didFinish?() case .error(let error): if error.recoverable { - insertCannula() + insertCannula(initState: .startingInsertion) } else { didRequestDeactivation?() } default: - insertCannula() + insertCannula(initState: .startingInsertion) + } + } + + func handlePossibleRestart() { + // if restarting, start without waiting for a button action + if restarting { + insertCannula(initState: .restarting) } } - } public extension OmnipodPumpManagerError { diff --git a/OmniKitUI/ViewModels/PairPodViewModel.swift b/OmniKitUI/ViewModels/PairPodViewModel.swift index c7c560b..dc7895a 100644 --- a/OmniKitUI/ViewModels/PairPodViewModel.swift +++ b/OmniKitUI/ViewModels/PairPodViewModel.swift @@ -42,6 +42,7 @@ class PairPodViewModel: ObservableObject, Identifiable { case priming(finishTime: CFTimeInterval) case error(OmnipodPairingError) case finished + case restarting var instructionsDisabled: Bool { switch self { @@ -60,7 +61,7 @@ class PairPodViewModel: ObservableObject, Identifiable { return LocalizedString("Pair pod.", comment: "Pairing action button accessibility label while ready to pair") case .pairing: return LocalizedString("Pairing.", comment: "Pairing action button accessibility label while pairing") - case .priming: + case .priming, .restarting: return LocalizedString("Priming. Please wait.", comment: "Pairing action button accessibility label while priming") case .error(let error): return String(format: "%@ %@", error.errorDescription ?? "", error.recoverySuggestion ?? "") @@ -77,7 +78,7 @@ class PairPodViewModel: ObservableObject, Identifiable { return LocalizedString("Retry", comment: "Pod pairing action button text while showing error") case .pairing: return LocalizedString("Pairing...", comment: "Pod pairing action button text while pairing") - case .priming: + case .priming, .restarting: return LocalizedString("Priming...", comment: "Pod pairing action button text while priming") case .finished: return LocalizedString("Continue", comment: "Pod pairing action button text when paired") @@ -116,7 +117,7 @@ class PairPodViewModel: ObservableObject, Identifiable { switch self { case .ready, .error: return .hidden - case .pairing: + case .pairing, .restarting: return .indeterminantProgress case .priming(let finishTime): return .timedProgress(finishTime: finishTime) @@ -127,7 +128,7 @@ class PairPodViewModel: ObservableObject, Identifiable { var isProcessing: Bool { switch self { - case .pairing, .priming: + case .pairing, .priming, .restarting: return true default: return false @@ -152,9 +153,16 @@ class PairPodViewModel: ObservableObject, Identifiable { @Published var state: PairPodViewModelState = .ready var podIsActivated: Bool { - return false // podPairer.podCommState != .noPod + return podPairer.getPodCommState != .noPod } + var restarting: Bool { + if case .restarting = self.state { + return true + } + return false + } + var backButtonHidden: Bool { if case .pairing = state { return true @@ -177,10 +185,10 @@ class PairPodViewModel: ObservableObject, Identifiable { self.podPairer = podPairer } - private func pair() { - state = .pairing + private func pairAndPrime(initState: PairPodViewModelState) { + state = initState - podPairer.pair { (status) in + podPairer.pairAndPrimePod { (status) in DispatchQueue.main.async { switch status { case .failure(let error): @@ -208,14 +216,21 @@ class PairPodViewModel: ObservableObject, Identifiable { self.didRequestDeactivation?() } else { // Retry - pair() + pairAndPrime(initState: .pairing) } case .finished: didFinish?() default: - pair() + pairAndPrime(initState: .pairing) } - } + } + + func handlePossibleRestart() { + // if restarting, start without waiting for a button action + if restarting { + pairAndPrime(initState: .restarting) + } + } } // Pairing recovery suggestions @@ -246,16 +261,21 @@ enum OmnipodPairingError : LocalizedError { } public protocol PodPairer { - func pair(completion: @escaping (PumpManagerResult) -> Void) + func pairAndPrimePod(completion: @escaping (PumpManagerResult) -> Void) func discardPod(completion: @escaping (Bool) -> ()) + var getPodCommState: PodCommState { get } } extension OmnipodPumpManager: PodPairer { public func discardPod(completion: @escaping (Bool) -> ()) { } - public func pair(completion: @escaping (PumpManagerResult) -> Void) { + public func pairAndPrimePod(completion: @escaping (PumpManagerResult) -> Void) { pairAndPrime(completion: completion) } + + public var getPodCommState: PodCommState { + return podCommState + } } diff --git a/OmniKitUI/ViewModels/PodLifeState.swift b/OmniKitUI/ViewModels/PodLifeState.swift index fc625c2..601b33f 100644 --- a/OmniKitUI/ViewModels/PodLifeState.swift +++ b/OmniKitUI/ViewModels/PodLifeState.swift @@ -72,7 +72,7 @@ enum PodLifeState { var nextPodLifecycleAction: OmnipodUIScreen { switch self { case .podActivating, .noPod: - return .pairPod + return .pairAndPrime default: return .deactivate } diff --git a/OmniKitUI/Views/InsertCannulaView.swift b/OmniKitUI/Views/InsertCannulaView.swift index c1761ff..c1c65ba 100644 --- a/OmniKitUI/Views/InsertCannulaView.swift +++ b/OmniKitUI/Views/InsertCannulaView.swift @@ -76,9 +76,13 @@ struct InsertCannulaView: View { .padding() } .alert(isPresented: $cancelModalIsPresented) { cancelPairingModal } - .navigationBarTitle("Insert Cannula", displayMode: .automatic) + .navigationBarTitle(LocalizedString("Insert Cannula", comment: "navigation bar title for insert cannula"), displayMode: .automatic) .navigationBarBackButtonHidden(true) .navigationBarItems(trailing: cancelButton) + .onFirstAppear { + // handle possible restart without waiting for a button action + self.viewModel.handlePossibleRestart() + } } var actionText : some View { Text(self.viewModel.state.nextActionButtonDescription) diff --git a/OmniKitUI/Views/PairPodView.swift b/OmniKitUI/Views/PairPodView.swift index ec88f68..d14f625 100644 --- a/OmniKitUI/Views/PairPodView.swift +++ b/OmniKitUI/Views/PairPodView.swift @@ -81,6 +81,10 @@ struct PairPodView: View { .navigationBarTitle(LocalizedString("Pair Pod", comment: "Navigation bar title for PairPodView"), displayMode: .automatic) .navigationBarBackButtonHidden(self.viewModel.backButtonHidden) .navigationBarItems(trailing: self.viewModel.state.navBarVisible ? cancelButton : nil) + .onFirstAppear { + // handle possible restart without waiting for a button action + self.viewModel.handlePossibleRestart() + } } var cancelButton: some View { From 8e80cf56363a7d7f1e72f847029dddc4d2f1601e Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 14 Feb 2024 19:33:46 -0800 Subject: [PATCH 7/8] Updated and improved resetPerPodPumpManagerState func logic + Use retractAlert func to handle the state.activeAlerts removal + Reset the state.acknowledgedTimeOffsetAlert var in case it gets used + Add call to resetPerPodPumpManagerState func from forgetPod() Simplified restart logic for insert cannula view restarts --- OmniKit/PumpManager/OmnipodPumpManager.swift | 42 +++++++++---------- .../ViewModels/InsertCannulaViewModel.swift | 14 +++---- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 98c8256..a24a0d7 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -666,6 +666,25 @@ extension OmnipodPumpManager { return date } + // Reset all the per pod state kept in pump manager state which doesn't span pods + fileprivate func resetPerPodPumpManagerState() { + + // Reset any residual per pod slot based pump manager alerts + // (i.e., all but timeOffsetChangeDetected which isn't actually used) + let podAlerts = state.activeAlerts.filter { $0 != .timeOffsetChangeDetected } + for alert in podAlerts { + self.retractAlert(alert: alert) + } + + self.setState { (state) in + // Reset alertsWithPendingAcknowledgment which are all pod slot based + state.alertsWithPendingAcknowledgment = [] + + // Reset other miscellaneous state variables that are actually per pod + state.podAttachmentConfirmed = false + state.acknowledgedTimeOffsetAlert = false + } + } // MARK: - Pod comms @@ -683,13 +702,14 @@ extension OmnipodPumpManager { podComms.forgetPod() + self.resetPerPodPumpManagerState() + if let dosesToStore = self.state.podState?.dosesToStore { self.store(doses: dosesToStore, completion: { error in self.setState({ (state) in if error != nil { state.unstoredDoses.append(contentsOf: dosesToStore) } - resetPodState(&state) }) completion() @@ -701,26 +721,6 @@ extension OmnipodPumpManager { completion() } } - - // Reset any PumpManager state that is really per pod state - private func resetPerPodPumpManagerState() { - setState({ (state) in - state.podAttachmentConfirmed = false - - // Remove any per pod triggeringSlot based pump manager alerts - // that don't span pods (i.e., all but timeOffsetChangeDetected). - for alert in state.activeAlerts { - if alert != .timeOffsetChangeDetected { - state.activeAlerts.remove(alert) - } - } - for alert in state.alertsWithPendingAcknowledgment { - if alert != .timeOffsetChangeDetected { - state.alertsWithPendingAcknowledgment.remove(alert) - } - } - }) - } // MARK: Testing #if targetEnvironment(simulator) diff --git a/OmniKitUI/ViewModels/InsertCannulaViewModel.swift b/OmniKitUI/ViewModels/InsertCannulaViewModel.swift index 564e44c..c1e8f4d 100644 --- a/OmniKitUI/ViewModels/InsertCannulaViewModel.swift +++ b/OmniKitUI/ViewModels/InsertCannulaViewModel.swift @@ -30,9 +30,9 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { var actionButtonAccessibilityLabel: String { switch self { - case .ready, .startingInsertion: + case .ready: return LocalizedString("Slide Button to insert Cannula", comment: "Insert cannula slider button accessibility label while ready to pair") - case .inserting, .restarting: + case .inserting, .startingInsertion, .restarting: return LocalizedString("Inserting. Please wait.", comment: "Insert cannula action button accessibility label while pairing") case .checkingInsertion: return LocalizedString("Checking Insertion", comment: "Insert cannula action button accessibility label checking insertion") @@ -177,8 +177,8 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { } } - private func insertCannula(initState: InsertCannulaViewModelState) { - state = initState + private func insertCannula() { + state = .startingInsertion cannulaInserter.insertCannula { (result) in DispatchQueue.main.async { switch(result) { @@ -213,19 +213,19 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { didFinish?() case .error(let error): if error.recoverable { - insertCannula(initState: .startingInsertion) + insertCannula() } else { didRequestDeactivation?() } default: - insertCannula(initState: .startingInsertion) + insertCannula() } } func handlePossibleRestart() { // if restarting, start without waiting for a button action if restarting { - insertCannula(initState: .restarting) + insertCannula() } } } From be827010f5b429af42051ca2259364c8485719f6 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Sun, 18 Feb 2024 22:10:49 -0800 Subject: [PATCH 8/8] Keep logic around resuming progress indicators in the view model + Remove cannulaInsertionSuccessfullyStarted var from pumpManager + Remove use of insertionWait variable from PodCommsSession.insertCannula() + Remove .restarting enum from PairPadViewModel & InsertCannulaViewModels + Remove restarting testing and viewModel state setting in UICoordinator + Remove restarting Bool var from PairPadViewModel & InsertCannulaViewModels + Remove handlePossibleRestart() from PairPadViewModel & InsertCannulViewModel + Remove use of onFirstAppear and delete now unneeded FirstAppear.swift + Improved PodPairer to use the pumpManager podCOmmState var + Update CannulaInserter to implement cannulaInsertionSuccessfullyStarted var + Update PairPodViewModelState priming value to use an optional + Have the view model init() funcs test for and handle resume cases --- OmniKit.xcodeproj/project.pbxproj | 4 -- OmniKit/PumpManager/OmnipodPumpManager.swift | 4 -- OmniKit/PumpManager/PodCommsSession.swift | 3 +- .../OmnipodUICoordinator.swift | 10 --- .../ViewModels/InsertCannulaViewModel.swift | 55 ++++----------- OmniKitUI/ViewModels/PairPodViewModel.swift | 69 ++++++++----------- OmniKitUI/Views/FirstAppear.swift | 30 -------- OmniKitUI/Views/InsertCannulaView.swift | 12 +--- OmniKitUI/Views/PairPodView.swift | 4 -- 9 files changed, 47 insertions(+), 144 deletions(-) delete mode 100644 OmniKitUI/Views/FirstAppear.swift diff --git a/OmniKit.xcodeproj/project.pbxproj b/OmniKit.xcodeproj/project.pbxproj index 9ba1ad6..d8b78df 100644 --- a/OmniKit.xcodeproj/project.pbxproj +++ b/OmniKit.xcodeproj/project.pbxproj @@ -192,7 +192,6 @@ D803399A2A500D3D004FF953 /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12401B229C7D8E900B32844 /* CRC8.swift */; }; D803399B2A50122F004FF953 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12401B329C7D8E900B32844 /* Packet.swift */; }; D803399C2A50128D004FF953 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0129C7DD4700435701 /* LocalizedString.swift */; }; - D8D9275E2B744A6C00D154B2 /* FirstAppear.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -436,7 +435,6 @@ C12EDA1529C7DFF100435701 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; C12EDA1729C7E01800435701 /* TimeZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; C12EDA1A29C7E06900435701 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; - D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstAppear.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -723,7 +721,6 @@ C124023129C7DA9700B32844 /* DesignElements */, C124023A29C7DA9700B32844 /* ExpirationReminderPickerView.swift */, C124024529C7DA9700B32844 /* ExpirationReminderSetupView.swift */, - D8D9275D2B744A6C00D154B2 /* FirstAppear.swift */, C124023629C7DA9700B32844 /* InsertCannulaView.swift */, C124023C29C7DA9700B32844 /* InsulinTypeConfirmation.swift */, C124023529C7DA9700B32844 /* LowReservoirReminderEditView.swift */, @@ -1217,7 +1214,6 @@ C124026E29C7DA9700B32844 /* FrameworkLocalText.swift in Sources */, C124028F29C7DA9700B32844 /* PodDetailsView.swift in Sources */, C124028629C7DA9700B32844 /* NotificationSettingsView.swift in Sources */, - D8D9275E2B744A6C00D154B2 /* FirstAppear.swift in Sources */, C124027D29C7DA9700B32844 /* InsertCannulaView.swift in Sources */, C12EDA0C29C7DED000435701 /* LocalizedString.swift in Sources */, C12EDA1629C7DFF100435701 /* HKUnit.swift in Sources */, diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index a24a0d7..5d2c068 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -589,10 +589,6 @@ extension OmnipodPumpManager { return .deactivating // Can't be reached and thus will never be returned } - public var cannulaInsertionSuccessfullyStarted: Bool { - return state.podState?.setupProgress.cannulaInsertionSuccessfullyStarted == true - } - public var podCommState: PodCommState { return podCommState(for: state) } diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index ada4404..e333f20 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -423,7 +423,6 @@ public class PodCommsSession { public func insertCannula(optionalAlerts: [PodAlert] = []) throws -> TimeInterval { let cannulaInsertionUnits = Pod.cannulaInsertionUnits + Pod.cannulaInsertionUnitsExtra - let insertionWait: TimeInterval = .seconds(cannulaInsertionUnits / Pod.primeDeliveryRate) guard let activatedAt = podState.activatedAt else { throw PodCommsError.noPodPaired @@ -465,7 +464,7 @@ public class PodCommsSession { podState.updateFromStatusResponse(status2, at: currentDate) podState.setupProgress = .cannulaInserting - return insertionWait + return status2.bolusNotDelivered / Pod.primeDeliveryRate // seconds for the cannula insert bolus to finish } public func checkInsertionCompleted() throws { diff --git a/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift b/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift index b188fd0..e3da75b 100644 --- a/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift +++ b/OmniKitUI/ViewControllers/OmnipodUICoordinator.swift @@ -214,11 +214,6 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl self?.navigateTo(.deactivate) } - if pumpManager.podCommState == .activating { - // Looks we are restarting, use an alternate restarting state to immediately continue - viewModel.state = .restarting - } - let view = hostingController(rootView: PairPodView(viewModel: viewModel).onAppear(perform: {UIApplication.shared.isIdleTimerDisabled = true}), onDisappear: {UIApplication.shared.isIdleTimerDisabled = false}) view.navigationItem.title = LocalizedString("Pair Pod", comment: "Title for pod pairing screen") view.navigationItem.backButtonDisplayMode = .generic @@ -248,11 +243,6 @@ class OmnipodUICoordinator: UINavigationController, PumpManagerOnboarding, Compl self?.navigateTo(.deactivate) } - if pumpManager.cannulaInsertionSuccessfullyStarted { - // Looks we are restarting, use an alternate restarting state to immediately continue - viewModel.state = .restarting - } - let view = hostingController(rootView: InsertCannulaView(viewModel: viewModel).onAppear(perform: {UIApplication.shared.isIdleTimerDisabled = true}), onDisappear: {UIApplication.shared.isIdleTimerDisabled = false}) view.navigationItem.title = LocalizedString("Insert Cannula", comment: "Title for insert cannula screen") view.navigationItem.hidesBackButton = true diff --git a/OmniKitUI/ViewModels/InsertCannulaViewModel.swift b/OmniKitUI/ViewModels/InsertCannulaViewModel.swift index c1e8f4d..bb96379 100644 --- a/OmniKitUI/ViewModels/InsertCannulaViewModel.swift +++ b/OmniKitUI/ViewModels/InsertCannulaViewModel.swift @@ -13,9 +13,14 @@ import OmniKit public protocol CannulaInserter { func insertCannula(completion: @escaping (Result) -> ()) func checkCannulaInsertionFinished(completion: @escaping (OmnipodPumpManagerError?) -> Void) + var cannulaInsertionSuccessfullyStarted: Bool { get } } -extension OmnipodPumpManager: CannulaInserter { } +extension OmnipodPumpManager: CannulaInserter { + public var cannulaInsertionSuccessfullyStarted: Bool { + return state.podState?.setupProgress.cannulaInsertionSuccessfullyStarted == true + } +} class InsertCannulaViewModel: ObservableObject, Identifiable { @@ -26,13 +31,12 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { case checkingInsertion case error(OmnipodPumpManagerError) case finished - case restarting var actionButtonAccessibilityLabel: String { switch self { case .ready: return LocalizedString("Slide Button to insert Cannula", comment: "Insert cannula slider button accessibility label while ready to pair") - case .inserting, .startingInsertion, .restarting: + case .inserting, .startingInsertion: return LocalizedString("Inserting. Please wait.", comment: "Insert cannula action button accessibility label while pairing") case .checkingInsertion: return LocalizedString("Checking Insertion", comment: "Insert cannula action button accessibility label checking insertion") @@ -58,7 +62,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { return LocalizedString("Slide to Insert Cannula", comment: "Cannula insertion button text while ready to insert") case .error: return LocalizedString("Retry", comment: "Cannula insertion button text while showing error") - case .inserting, .startingInsertion, .restarting: + case .inserting, .startingInsertion: return LocalizedString("Inserting...", comment: "Cannula insertion button text while inserting") case .checkingInsertion: return LocalizedString("Checking...", comment: "Cannula insertion button text while checking insertion") @@ -83,7 +87,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { switch self { case .ready, .error: return .hidden - case .startingInsertion, .checkingInsertion, .restarting: + case .startingInsertion, .checkingInsertion: return .indeterminantProgress case .inserting(let finishTime): return .timedProgress(finishTime: finishTime) @@ -103,7 +107,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { var isProcessing: Bool { switch self { - case .startingInsertion, .inserting, .restarting: + case .startingInsertion, .inserting: return true default: return false @@ -135,13 +139,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { } } - var restarting: Bool { - if case .restarting = self.state { - return true - } - return false - } - var didFinish: (() -> Void)? var didRequestDeactivation: (() -> Void)? @@ -150,20 +147,13 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { init(cannulaInserter: CannulaInserter) { self.cannulaInserter = cannulaInserter + + // If resuming, don't wait for the button action + if cannulaInserter.cannulaInsertionSuccessfullyStarted { + insertCannula() + } } -// private func handleEvent(_ event: ActivationStep2Event) { -// switch event { -// case .insertingCannula: -// let finishTime = TimeInterval(Pod.estimatedCannulaInsertionDuration) -// state = .inserting(finishTime: CACurrentMediaTime() + finishTime) -// case .step2Completed: -// state = .finished -// default: -// break -// } -// } - private func checkCannulaInsertionFinished() { state = .checkingInsertion cannulaInserter.checkCannulaInsertionFinished() { (error) in @@ -196,14 +186,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { self.state = .error(error) } } - - -// switch status { -// case .error(let error): -// self.state = .error(error) -// case .event(let event): -// self.handleEvent(event) -// } } } @@ -221,13 +203,6 @@ class InsertCannulaViewModel: ObservableObject, Identifiable { insertCannula() } } - - func handlePossibleRestart() { - // if restarting, start without waiting for a button action - if restarting { - insertCannula() - } - } } public extension OmnipodPumpManagerError { diff --git a/OmniKitUI/ViewModels/PairPodViewModel.swift b/OmniKitUI/ViewModels/PairPodViewModel.swift index dc7895a..ffd79ee 100644 --- a/OmniKitUI/ViewModels/PairPodViewModel.swift +++ b/OmniKitUI/ViewModels/PairPodViewModel.swift @@ -39,10 +39,9 @@ class PairPodViewModel: ObservableObject, Identifiable { enum PairPodViewModelState { case ready case pairing - case priming(finishTime: CFTimeInterval) + case priming(finishTime: CFTimeInterval?) case error(OmnipodPairingError) case finished - case restarting var instructionsDisabled: Bool { switch self { @@ -61,7 +60,7 @@ class PairPodViewModel: ObservableObject, Identifiable { return LocalizedString("Pair pod.", comment: "Pairing action button accessibility label while ready to pair") case .pairing: return LocalizedString("Pairing.", comment: "Pairing action button accessibility label while pairing") - case .priming, .restarting: + case .priming: return LocalizedString("Priming. Please wait.", comment: "Pairing action button accessibility label while priming") case .error(let error): return String(format: "%@ %@", error.errorDescription ?? "", error.recoverySuggestion ?? "") @@ -78,7 +77,7 @@ class PairPodViewModel: ObservableObject, Identifiable { return LocalizedString("Retry", comment: "Pod pairing action button text while showing error") case .pairing: return LocalizedString("Pairing...", comment: "Pod pairing action button text while pairing") - case .priming, .restarting: + case .priming: return LocalizedString("Priming...", comment: "Pod pairing action button text while priming") case .finished: return LocalizedString("Continue", comment: "Pod pairing action button text when paired") @@ -86,14 +85,6 @@ class PairPodViewModel: ObservableObject, Identifiable { } var navBarButtonAction: NavBarButtonAction { -// switch self { -// case .error(_, let podCommState): -// if podCommState == .activating { -// return .discard -// } -// default: -// break -// } return .cancel } @@ -117,10 +108,14 @@ class PairPodViewModel: ObservableObject, Identifiable { switch self { case .ready, .error: return .hidden - case .pairing, .restarting: + case .pairing: return .indeterminantProgress case .priming(let finishTime): - return .timedProgress(finishTime: finishTime) + if let finishTime { + return .timedProgress(finishTime: finishTime) + } else { + return .indeterminantProgress + } case .finished: return .completed } @@ -128,7 +123,7 @@ class PairPodViewModel: ObservableObject, Identifiable { var isProcessing: Bool { switch self { - case .pairing, .priming, .restarting: + case .pairing, .priming: return true default: return false @@ -153,16 +148,9 @@ class PairPodViewModel: ObservableObject, Identifiable { @Published var state: PairPodViewModelState = .ready var podIsActivated: Bool { - return podPairer.getPodCommState != .noPod + return podPairer.podCommState != .noPod } - var restarting: Bool { - if case .restarting = self.state { - return true - } - return false - } - var backButtonHidden: Bool { if case .pairing = state { return true @@ -183,11 +171,21 @@ class PairPodViewModel: ObservableObject, Identifiable { init(podPairer: PodPairer) { self.podPairer = podPairer + + // If resuming, don't wait for the button action + if podPairer.podCommState == .activating { + pairAndPrime() + } } - - private func pairAndPrime(initState: PairPodViewModelState) { - state = initState - + + private func pairAndPrime() { + if podPairer.podCommState == .noPod { + state = .pairing + } else { + // Already paired, so resume with the prime + state = .priming(finishTime: nil) + } + podPairer.pairAndPrimePod { (status) in DispatchQueue.main.async { switch status { @@ -216,19 +214,12 @@ class PairPodViewModel: ObservableObject, Identifiable { self.didRequestDeactivation?() } else { // Retry - pairAndPrime(initState: .pairing) + pairAndPrime() } case .finished: didFinish?() default: - pairAndPrime(initState: .pairing) - } - } - - func handlePossibleRestart() { - // if restarting, start without waiting for a button action - if restarting { - pairAndPrime(initState: .restarting) + pairAndPrime() } } } @@ -263,7 +254,7 @@ enum OmnipodPairingError : LocalizedError { public protocol PodPairer { func pairAndPrimePod(completion: @escaping (PumpManagerResult) -> Void) func discardPod(completion: @escaping (Bool) -> ()) - var getPodCommState: PodCommState { get } + var podCommState: PodCommState { get } } extension OmnipodPumpManager: PodPairer { @@ -273,9 +264,5 @@ extension OmnipodPumpManager: PodPairer { public func pairAndPrimePod(completion: @escaping (PumpManagerResult) -> Void) { pairAndPrime(completion: completion) } - - public var getPodCommState: PodCommState { - return podCommState - } } diff --git a/OmniKitUI/Views/FirstAppear.swift b/OmniKitUI/Views/FirstAppear.swift deleted file mode 100644 index 25f49bb..0000000 --- a/OmniKitUI/Views/FirstAppear.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// FirstAppear.swift -// OmniKit -// -// Created by Joe Moran on 9/24/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import SwiftUI - -extension View { - func onFirstAppear(_ action: @escaping () -> ()) -> some View { - modifier(FirstAppear(action: action)) - } -} - -private struct FirstAppear: ViewModifier { - let action: () -> () - - // State used to insure action is invoked here only once - @State private var hasAppeared = false - - func body(content: Content) -> some View { - content.onAppear { - guard !hasAppeared else { return } - hasAppeared = true - action() - } - } -} diff --git a/OmniKitUI/Views/InsertCannulaView.swift b/OmniKitUI/Views/InsertCannulaView.swift index c1c65ba..bf8aec8 100644 --- a/OmniKitUI/Views/InsertCannulaView.swift +++ b/OmniKitUI/Views/InsertCannulaView.swift @@ -79,10 +79,6 @@ struct InsertCannulaView: View { .navigationBarTitle(LocalizedString("Insert Cannula", comment: "navigation bar title for insert cannula"), displayMode: .automatic) .navigationBarBackButtonHidden(true) .navigationBarItems(trailing: cancelButton) - .onFirstAppear { - // handle possible restart without waiting for a button action - self.viewModel.handlePossibleRestart() - } } var actionText : some View { Text(self.viewModel.state.nextActionButtonDescription) @@ -135,7 +131,7 @@ struct InsertCannulaView: View { } class MockCannulaInserter: CannulaInserter { - public func insertCannula(completion: @escaping (Result) -> Void) { + func insertCannula(completion: @escaping (Result) -> Void) { let mockDelay = TimeInterval(seconds: 3) let result :Result = .success(mockDelay) completion(result) @@ -145,15 +141,13 @@ class MockCannulaInserter: CannulaInserter { completion(nil) } - + var cannulaInsertionSuccessfullyStarted: Bool = false } + struct InsertCannulaView_Previews: PreviewProvider { static var mockInserter = MockCannulaInserter() static var model = InsertCannulaViewModel(cannulaInserter: mockInserter) static var previews: some View { InsertCannulaView(viewModel: model) - - - } } diff --git a/OmniKitUI/Views/PairPodView.swift b/OmniKitUI/Views/PairPodView.swift index d14f625..ec88f68 100644 --- a/OmniKitUI/Views/PairPodView.swift +++ b/OmniKitUI/Views/PairPodView.swift @@ -81,10 +81,6 @@ struct PairPodView: View { .navigationBarTitle(LocalizedString("Pair Pod", comment: "Navigation bar title for PairPodView"), displayMode: .automatic) .navigationBarBackButtonHidden(self.viewModel.backButtonHidden) .navigationBarItems(trailing: self.viewModel.state.navBarVisible ? cancelButton : nil) - .onFirstAppear { - // handle possible restart without waiting for a button action - self.viewModel.handlePossibleRestart() - } } var cancelButton: some View {