Skip to content

Commit 20eddc2

Browse files
authored
Fix issues with bolus view, and show forecast info modal (#505)
1 parent 2881851 commit 20eddc2

File tree

5 files changed

+169
-171
lines changed

5 files changed

+169
-171
lines changed

Loop/View Controllers/CarbEntryViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
500500
potentialCarbEntry: updatedEntry,
501501
selectedCarbAbsorptionTimeEmoji: selectedDefaultAbsorptionTimeEmoji
502502
)
503+
viewModel.generateRecommendationAndStartObserving()
503504

504505
let bolusEntryView = BolusEntryView(viewModel: viewModel).environmentObject(deviceManager.displayGlucoseUnitObservable)
505506

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,7 @@ final class StatusTableViewController: LoopChartsTableViewController {
12471247
hostingController = DismissibleHostingController(rootView: bolusEntryView, isModalInPresentation: false)
12481248
} else {
12491249
let viewModel = BolusEntryViewModel(delegate: deviceManager, isManualGlucoseEntryEnabled: enableManualGlucoseEntry)
1250+
viewModel.generateRecommendationAndStartObserving()
12501251
let bolusEntryView = BolusEntryView(viewModel: viewModel).environmentObject(deviceManager.displayGlucoseUnitObservable)
12511252
hostingController = DismissibleHostingController(rootView: bolusEntryView, isModalInPresentation: false)
12521253
}

Loop/View Models/BolusEntryViewModel.swift

Lines changed: 51 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)