@@ -36,8 +36,6 @@ protocol BolusEntryViewModelDelegate: AnyObject {
3636
3737 func carbsOnBoard( at date: Date , effectVelocities: [ GlucoseEffectVelocity ] ? , completion: @escaping ( _ result: CarbStoreResult < CarbValue > ) -> Void )
3838
39- func ensureCurrentPumpData( completion: @escaping ( Date ? ) -> Void )
40-
4139 func insulinActivityDuration( for type: InsulinType ? ) -> TimeInterval
4240
4341 var mostRecentGlucoseDataDate : Date ? { get }
@@ -63,6 +61,7 @@ final class BolusEntryViewModel: ObservableObject {
6361 case manualGlucoseEntryOutOfAcceptableRange
6462 case manualGlucoseEntryPersistenceFailure
6563 case glucoseNoLongerStale
64+ case forecastInfo
6665 }
6766
6867 enum Notice : Equatable {
@@ -101,6 +100,7 @@ final class BolusEntryViewModel: ObservableObject {
101100
102101 @Published var recommendedBolus : HKQuantity ?
103102 @Published var enteredBolus = HKQuantity ( unit: . internationalUnit( ) , doubleValue: 0 )
103+ private var userChangedBolusAmount = false
104104 private var isInitiatingSaveOrBolus = false
105105
106106 private var dosingDecision = BolusDosingDecision ( for: . normalBolus)
@@ -111,8 +111,6 @@ final class BolusEntryViewModel: ObservableObject {
111111 private let log = OSLog ( category: " BolusEntryViewModel " )
112112 private var cancellables : Set < AnyCancellable > = [ ]
113113
114- @Published var isRefreshingPump : Bool = false
115-
116114 let chartManager : ChartsManager = {
117115 let predictedGlucoseChart = PredictedGlucoseChart ( predictedGlucoseBounds: FeatureFlags . predictedGlucoseChartClampEnabled ? . default : nil ,
118116 yAxisStepSizeMGDLOverride: FeatureFlags . predictedGlucoseChartClampEnabled ? 40 : nil )
@@ -212,26 +210,40 @@ final class BolusEntryViewModel: ObservableObject {
212210 self . dosingDecision. originalCarbEntry = originalCarbEntry
213211
214212 self . cachedDisplayGlucoseUnit = delegate. displayGlucoseUnitObservable. displayGlucoseUnit
215- observeLoopUpdates ( )
216- observeEnteredBolusChanges ( )
217- observeEnteredManualGlucoseChanges ( )
218- observeElapsedTime ( )
219- observeRecommendedBolusChanges ( )
220- update ( )
213+
214+ self . updateSettings ( )
221215 }
222-
216+
217+ public func generateRecommendationAndStartObserving( _ completion: ( ( ) -> Void ) ? = nil ) {
218+ update ( ) {
219+ // Only start observing after first update is complete
220+ self . observeLoopUpdates ( )
221+ self . observeEnteredManualGlucoseChanges ( )
222+ self . observeElapsedTime ( )
223+ self . observeEnteredBolusChanges ( )
224+ completion ? ( )
225+ }
226+ }
227+
223228 private func observeLoopUpdates( ) {
224229 NotificationCenter . default
225230 . publisher ( for: . LoopDataUpdated)
226231 . receive ( on: DispatchQueue . main)
227- . sink { [ weak self] _ in
232+ . sink { [ weak self] note in
233+ if let rawContext = note. userInfo ? [ LoopDataManager . LoopUpdateContextKey] as? LoopDataManager . LoopUpdateContext . RawValue ,
234+ let context = LoopDataManager . LoopUpdateContext ( rawValue: rawContext) ,
235+ context == . preferences
236+ {
237+ self ? . updateSettings ( )
238+ }
228239 self ? . update ( )
229240 }
230241 . store ( in: & cancellables)
231242 }
232243
233244 private func observeEnteredBolusChanges( ) {
234245 $enteredBolus
246+ . dropFirst ( )
235247 . removeDuplicates ( )
236248 . debounce ( for: . milliseconds( debounceIntervalMilliseconds) , scheduler: RunLoop . main)
237249 . sink { [ weak self] _ in
@@ -242,25 +254,16 @@ final class BolusEntryViewModel: ObservableObject {
242254 . store ( in: & cancellables)
243255 }
244256
245- private func observeRecommendedBolusChanges( ) {
246- $recommendedBolus
247- . removeDuplicates ( )
248- . debounce ( for: . milliseconds( debounceIntervalMilliseconds) , scheduler: RunLoop . main)
249- . sink { [ weak self] _ in
250- self ? . setRecommendedBolus ( )
251- }
252- . store ( in: & cancellables)
253- }
254-
255257 private func observeEnteredManualGlucoseChanges( ) {
256258 $manualGlucoseQuantity
259+ . dropFirst ( )
257260 . debounce ( for: . milliseconds( debounceIntervalMilliseconds) , scheduler: RunLoop . main)
258261 . sink { [ weak self] enteredManualGlucose in
259262 guard let self = self else { return }
260263
261264 self . updateManualGlucoseSample ( enteredAt: self . now ( ) )
262265
263- // Clear out any entered bolus whenever the manual entry changes
266+ // Clear out any entered bolus whenever the glucose entry changes
264267 self . enteredBolus = HKQuantity ( unit: . internationalUnit( ) , doubleValue: 0 )
265268
266269 self . delegate? . withLoopState { [ weak self] state in
@@ -269,9 +272,7 @@ final class BolusEntryViewModel: ObservableObject {
269272 self ? . updateGlucoseChartValues ( )
270273 } )
271274
272- self ? . ensurePumpDataIsFresh { [ weak self] in
273- self ? . updateRecommendedBolusAndNotice ( from: state, isUpdatingFromUserInput: true )
274- }
275+ self ? . updateRecommendedBolusAndNotice ( from: state, isUpdatingFromUserInput: true )
275276 }
276277 }
277278 . store ( in: & cancellables)
@@ -305,15 +306,6 @@ final class BolusEntryViewModel: ObservableObject {
305306 return recommendedBolus. doubleValue ( for: . internationalUnit( ) ) > 0
306307 }
307308
308- func setRecommendedBolus( ) {
309- guard isBolusRecommended else { return }
310- enteredBolus = recommendedBolus!
311- isRefreshingPump = false
312- delegate? . withLoopState { [ weak self] state in
313- self ? . updatePredictedGlucoseValues ( from: state)
314- }
315- }
316-
317309 func saveAndDeliver( onSuccess completion: @escaping ( ) -> Void ) {
318310 guard delegate? . isPumpConfigured ?? false else {
319311 presentAlert ( . noPumpManagerConfigured)
@@ -513,17 +505,22 @@ final class BolusEntryViewModel: ObservableObject {
513505 }
514506 }
515507
516- private func update( ) {
508+ private func update( _ completion : ( ( ) -> Void ) ? = nil ) {
517509 dispatchPrecondition ( condition: . onQueue( . main) )
518510
519511 // Prevent any UI updates after a bolus has been initiated.
520- guard !isInitiatingSaveOrBolus else { return }
512+ guard !isInitiatingSaveOrBolus else {
513+ completion ? ( )
514+ return
515+ }
521516
522517 disableManualGlucoseEntryIfNecessary ( )
523518 updateChartDateInterval ( )
524519 updateStoredGlucoseValues ( )
525- updateFromLoopState ( )
526- updateActiveInsulin ( )
520+ updatePredictionAndRecommendation ( ) {
521+ self . updateActiveInsulin ( )
522+ completion ? ( )
523+ }
527524 }
528525
529526 private func disableManualGlucoseEntryIfNecessary( ) {
@@ -626,15 +623,17 @@ final class BolusEntryViewModel: ObservableObject {
626623 }
627624 }
628625
629- private func updateFromLoopState( ) {
630- delegate? . withLoopState { [ weak self] state in
631- self ? . updatePredictedGlucoseValues ( from: state)
626+ private func updatePredictionAndRecommendation( _ completion: @escaping ( ) -> Void ) {
627+ guard let delegate = delegate else {
628+ completion ( )
629+ return
630+ }
631+ delegate. withLoopState { [ weak self] state in
632632 self ? . updateCarbsOnBoard ( from: state)
633- self ? . ensurePumpDataIsFresh { [ weak self] in
634- self ? . updateRecommendedBolusAndNotice ( from: state, isUpdatingFromUserInput: false )
635- DispatchQueue . main. async {
636- self ? . updateSettings ( )
637- }
633+ self ? . updateRecommendedBolusAndNotice ( from: state, isUpdatingFromUserInput: false )
634+ self ? . updatePredictedGlucoseValues ( from: state)
635+ DispatchQueue . main. async {
636+ completion ( )
638637 }
639638 }
640639 }
@@ -654,32 +653,6 @@ final class BolusEntryViewModel: ObservableObject {
654653 }
655654 }
656655
657- private func ensurePumpDataIsFresh( then completion: @escaping ( ) -> Void ) {
658- if !isPumpDataStale {
659- completion ( )
660- return
661- }
662-
663- DispatchQueue . main. async {
664- // v-- This needs to happen on the main queue
665- self . isRefreshingPump = true
666- let wrappedCompletion : ( Date ? ) -> Void = { [ weak self] ( lastSync) in
667- self ? . delegate? . withLoopState { [ weak self] _ in
668- // v-- This needs to happen in LoopDataManager's dataQueue
669- completion ( )
670- // ^v-- Unfortunately, these two things might happen concurrently, so in theory the
671- // completion above may not "complete" by the time we clear isRefreshingPump. To fix that
672- // would require some very confusing wiring, but I think it is a minor issue.
673- DispatchQueue . main. async {
674- // v-- This needs to happen on the main queue
675- self ? . isRefreshingPump = false
676- }
677- }
678- }
679- self . delegate? . ensureCurrentPumpData ( completion: wrappedCompletion)
680- }
681- }
682-
683656 private func updateRecommendedBolusAndNotice( from state: LoopState , isUpdatingFromUserInput: Bool ) {
684657 dispatchPrecondition ( condition: . notOnQueue( . main) )
685658
@@ -729,15 +702,12 @@ final class BolusEntryViewModel: ObservableObject {
729702 self . dosingDecision. manualBolusRecommendation = recommendation. map { ManualBolusRecommendationWithDate ( recommendation: $0, date: now) }
730703 self . activeNotice = notice
731704
732- if priorRecommendedBolus != recommendedBolus,
733- self . enteredBolus. doubleValue ( for: . internationalUnit( ) ) > 0 ,
734- !self . isInitiatingSaveOrBolus
705+ if priorRecommendedBolus != nil ,
706+ priorRecommendedBolus != recommendedBolus,
707+ !self . isInitiatingSaveOrBolus,
708+ !isUpdatingFromUserInput
735709 {
736- self . enteredBolus = HKQuantity ( unit: . internationalUnit( ) , doubleValue: 0 )
737-
738- if !isUpdatingFromUserInput {
739- self . presentAlert ( . recommendationChanged)
740- }
710+ self . presentAlert ( . recommendationChanged)
741711 }
742712 }
743713 }
0 commit comments