Skip to content

Commit a120397

Browse files
authored
Add carb entry override warning (#1892)
If insulin needs are modified by an active override modified, present an appropriate warning cell.
1 parent cb5870a commit a120397

File tree

1 file changed

+108
-31
lines changed

1 file changed

+108
-31
lines changed

Loop/View Controllers/CarbEntryViewController.swift

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,31 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
111111
private var shouldDisplayAccurateCarbEntryWarning = false {
112112
didSet {
113113
if shouldDisplayAccurateCarbEntryWarning != oldValue {
114-
self.displayAccuracyWarning()
114+
if shouldDisplayOverrideEnabledWarning {
115+
self.displayWarningRow(rowType: WarningRow.carbEntry, isAddingRow: shouldDisplayAccurateCarbEntryWarning)
116+
} else {
117+
self.shouldDisplayWarning = shouldDisplayAccurateCarbEntryWarning || shouldDisplayOverrideEnabledWarning
118+
}
119+
}
120+
}
121+
}
122+
123+
private var shouldDisplayOverrideEnabledWarning = false {
124+
didSet {
125+
if shouldDisplayOverrideEnabledWarning != oldValue {
126+
if shouldDisplayAccurateCarbEntryWarning {
127+
self.displayWarningRow(rowType: WarningRow.override, isAddingRow: shouldDisplayOverrideEnabledWarning)
128+
} else {
129+
self.shouldDisplayWarning = shouldDisplayOverrideEnabledWarning || shouldDisplayAccurateCarbEntryWarning
130+
}
131+
}
132+
}
133+
}
134+
135+
private var shouldDisplayWarning = false {
136+
didSet {
137+
if shouldDisplayWarning != oldValue {
138+
self.displayWarning()
115139
}
116140
}
117141
}
@@ -183,18 +207,20 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
183207
override func viewDidAppear(_ animated: Bool) {
184208
super.viewDidAppear(animated)
185209

186-
if shouldBeginEditingQuantity, let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.value.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayAccurateCarbEntryWarning))) as? DecimalTextFieldTableViewCell {
210+
if shouldBeginEditingQuantity, let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.value.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayWarning))) as? DecimalTextFieldTableViewCell {
187211
shouldBeginEditingQuantity = false
188212
cell.textField.becomeFirstResponder()
189213
}
190214

191-
// check if the warning should be displayed
215+
// check if either warning should be displayed
192216
updateDisplayAccurateCarbEntryWarning()
217+
updateDisplayOverrideEnabledWarning()
193218

194219
// monitor loop updates
195220
notificationObservers += [
196221
NotificationCenter.default.addObserver(forName: .LoopDataUpdated, object: deviceManager.loopManager, queue: nil) { [weak self] _ in
197222
self?.updateDisplayAccurateCarbEntryWarning()
223+
self?.updateDisplayOverrideEnabledWarning()
198224
}
199225
]
200226
}
@@ -243,17 +269,46 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
243269
}
244270
}
245271

246-
private func displayAccuracyWarning() {
272+
private func updateDisplayOverrideEnabledWarning() {
273+
DispatchQueue.main.async {
274+
if let managerSettings = self.deviceManager?.settings {
275+
if !managerSettings.scheduleOverrideEnabled(at: Date()) {
276+
self.shouldDisplayOverrideEnabledWarning = false
277+
} else if let overrideSettings = managerSettings.scheduleOverride?.settings {
278+
self.shouldDisplayOverrideEnabledWarning = overrideSettings.effectiveInsulinNeedsScaleFactor != 1.0
279+
}
280+
}
281+
}
282+
}
283+
284+
private func displayWarning() {
247285
tableView.beginUpdates()
248286

249-
if shouldDisplayAccurateCarbEntryWarning {
287+
if shouldDisplayWarning {
250288
tableView.insertSections([Sections.warning.rawValue], with: .top)
251289
} else {
252290
tableView.deleteSections([Sections.warning.rawValue], with: .top)
253291
}
254292

255293
tableView.endUpdates()
256294
}
295+
296+
private func displayWarningRow(rowType: WarningRow, isAddingRow: Bool = true ) {
297+
if shouldDisplayWarning {
298+
tableView.beginUpdates()
299+
300+
// If the accurate carb entry warning is shown, use the positional index of the given row type.
301+
let rowIndex = shouldDisplayAccurateCarbEntryWarning ? rowType.rawValue : 0
302+
303+
if isAddingRow {
304+
tableView.insertRows(at: [IndexPath(row: rowIndex, section: Sections.warning.rawValue)], with: UITableView.RowAnimation.top)
305+
} else {
306+
tableView.deleteRows(at: [IndexPath(row: rowIndex, section: Sections.warning.rawValue)], with: UITableView.RowAnimation.top)
307+
}
308+
309+
tableView.endUpdates()
310+
}
311+
}
257312

258313
// MARK: - Table view data source
259314
fileprivate enum Sections: Int, CaseIterable {
@@ -272,9 +327,9 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
272327
return displayWarningSection ? indexPath.section : indexPath.section + 1
273328
}
274329

275-
static func numberOfRows(for section: Int, displayWarningSection: Bool) -> Int {
276-
if section == Sections.warning.rawValue && displayWarningSection {
277-
return 1
330+
static func numberOfRows(for section: Int, displayCarbEntryWarning: Bool, displayOverrideWarning: Bool) -> Int {
331+
if section == Sections.warning.rawValue && (displayCarbEntryWarning || displayOverrideWarning) {
332+
return displayCarbEntryWarning && displayOverrideWarning ? WarningRow.allCases.count : WarningRow.allCases.count - 1
278333
}
279334

280335
return DetailsRow.allCases.count
@@ -307,32 +362,54 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
307362
case foodType
308363
case absorptionTime
309364
}
365+
366+
fileprivate enum WarningRow: Int, CaseIterable {
367+
case carbEntry
368+
case override
369+
}
310370

311371
override func numberOfSections(in tableView: UITableView) -> Int {
312-
return Sections.numberOfSections(displayWarningSection: shouldDisplayAccurateCarbEntryWarning)
372+
return Sections.numberOfSections(displayWarningSection: shouldDisplayWarning)
313373
}
314374

315375
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
316-
return Sections.numberOfRows(for: section, displayWarningSection: shouldDisplayAccurateCarbEntryWarning)
376+
return Sections.numberOfRows(for: section, displayCarbEntryWarning: shouldDisplayAccurateCarbEntryWarning, displayOverrideWarning: shouldDisplayOverrideEnabledWarning)
317377
}
318378

319379
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
320-
switch Sections(rawValue: Sections.section(for: indexPath, displayWarningSection: shouldDisplayAccurateCarbEntryWarning))! {
380+
switch Sections(rawValue: Sections.section(for: indexPath, displayWarningSection: shouldDisplayWarning))! {
321381
case .warning:
322382
let cell: UITableViewCell
323-
if let existingCell = tableView.dequeueReusableCell(withIdentifier: "CarbEntryAccuracyWarningCell") {
324-
cell = existingCell
383+
// if no accurate carb entry warning should be shown OR if the given indexPath is for the override warning row, return the override warning cell.
384+
if !shouldDisplayAccurateCarbEntryWarning || WarningRow(rawValue: indexPath.row)! == .override {
385+
if let existingCell = tableView.dequeueReusableCell(withIdentifier: "CarbEntryOverrideEnabledWarningCell") {
386+
cell = existingCell
387+
} else {
388+
cell = UITableViewCell(style: .default, reuseIdentifier: "CarbEntryOverrideEnabledWarningCell")
389+
}
390+
391+
cell.imageView?.image = UIImage(systemName: "exclamationmark.triangle.fill")
392+
cell.imageView?.tintColor = .warning
393+
cell.textLabel?.numberOfLines = 0
394+
cell.textLabel?.text = NSLocalizedString("An active override is modifying your carb ratio and insulin sensitivity. If you don't want this to affect your bolus calculation and projected glucose, consider turning off the override.", comment: "Warning to ensure the carb entry is accurate during an override")
395+
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: .caption1)
396+
cell.textLabel?.textColor = .secondaryLabel
397+
cell.isUserInteractionEnabled = false
325398
} else {
326-
cell = UITableViewCell(style: .default, reuseIdentifier: "CarbEntryAccuracyWarningCell")
399+
if let existingCell = tableView.dequeueReusableCell(withIdentifier: "CarbEntryAccuracyWarningCell") {
400+
cell = existingCell
401+
} else {
402+
cell = UITableViewCell(style: .default, reuseIdentifier: "CarbEntryAccuracyWarningCell")
403+
}
404+
405+
cell.imageView?.image = UIImage(systemName: "exclamationmark.triangle.fill")
406+
cell.imageView?.tintColor = .destructive
407+
cell.textLabel?.numberOfLines = 0
408+
cell.textLabel?.text = NSLocalizedString("Your glucose is rapidly rising. Check that any carbs you've eaten were logged. If you logged carbs, check that the time you entered lines up with when you started eating.", comment: "Warning to ensure the carb entry is accurate")
409+
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: .caption1)
410+
cell.textLabel?.textColor = .secondaryLabel
411+
cell.isUserInteractionEnabled = false
327412
}
328-
329-
cell.imageView?.image = UIImage(systemName: "exclamationmark.triangle.fill")
330-
cell.imageView?.tintColor = .destructive
331-
cell.textLabel?.numberOfLines = 0
332-
cell.textLabel?.text = NSLocalizedString("Your glucose is rapidly rising. Check that any carbs you've eaten were logged. If you logged carbs, check that the time you entered lines up with when you started eating.", comment: "Warning to ensure the carb entry is accurate")
333-
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: .caption1)
334-
cell.textLabel?.textColor = .secondaryLabel
335-
cell.isUserInteractionEnabled = false
336413
return cell
337414
case .details:
338415
switch DetailsRow(rawValue: indexPath.row)! {
@@ -415,7 +492,7 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
415492
}
416493

417494
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
418-
switch Sections(rawValue: Sections.section(for: indexPath, displayWarningSection: shouldDisplayAccurateCarbEntryWarning)) {
495+
switch Sections(rawValue: Sections.section(for: indexPath, displayWarningSection: shouldDisplayWarning)) {
419496
case .details:
420497
switch DetailsRow(rawValue: indexPath.row)! {
421498
case .value, .date:
@@ -434,15 +511,15 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
434511
}
435512

436513
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
437-
return Sections.footer(for: section, displayWarningSection: shouldDisplayAccurateCarbEntryWarning)
514+
return Sections.footer(for: section, displayWarningSection: shouldDisplayWarning)
438515
}
439516

440517
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
441-
return Sections.headerHeight(for: section, displayWarningSection: shouldDisplayAccurateCarbEntryWarning)
518+
return Sections.headerHeight(for: section, displayWarningSection: shouldDisplayWarning)
442519
}
443520

444521
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
445-
return Sections.footerHeight(for: section, displayWarningSection: shouldDisplayAccurateCarbEntryWarning)
522+
return Sections.footerHeight(for: section, displayWarningSection: shouldDisplayWarning)
446523
}
447524

448525
// MARK: - UITableViewDelegate
@@ -459,7 +536,7 @@ final class CarbEntryViewController: LoopChartsTableViewController, Identifiable
459536
case is FoodTypeShortcutCell:
460537
usesCustomFoodType = true
461538
shouldBeginEditingFoodType = true
462-
tableView.reloadRows(at: [IndexPath(row: DetailsRow.foodType.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayAccurateCarbEntryWarning))], with: .none)
539+
tableView.reloadRows(at: [IndexPath(row: DetailsRow.foodType.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayWarning))], with: .none)
463540
default:
464541
break
465542
}
@@ -686,14 +763,14 @@ extension CarbEntryViewController: FoodTypeShortcutCellDelegate {
686763
tableView.beginUpdates()
687764
usesCustomFoodType = true
688765
shouldBeginEditingFoodType = true
689-
tableView.reloadRows(at: [IndexPath(row: DetailsRow.foodType.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayAccurateCarbEntryWarning))], with: .fade)
766+
tableView.reloadRows(at: [IndexPath(row: DetailsRow.foodType.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayWarning))], with: .fade)
690767
tableView.endUpdates()
691768
}
692769

693770
if let absorptionTime = absorptionTime {
694771
self.absorptionTime = absorptionTime
695772

696-
if let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.absorptionTime.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayAccurateCarbEntryWarning))) as? DateAndDurationTableViewCell {
773+
if let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.absorptionTime.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayWarning))) as? DateAndDurationTableViewCell {
697774
cell.duration = absorptionTime
698775
}
699776
}
@@ -705,7 +782,7 @@ extension CarbEntryViewController: FoodTypeShortcutCellDelegate {
705782

706783
extension CarbEntryViewController: EmojiInputControllerDelegate {
707784
func emojiInputControllerDidAdvanceToStandardInputMode(_ controller: EmojiInputController) {
708-
if let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.foodType.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayAccurateCarbEntryWarning))) as? TextFieldTableViewCell, let textField = cell.textField as? CustomInputTextField, textField.customInput != nil {
785+
if let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.foodType.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayWarning))) as? TextFieldTableViewCell, let textField = cell.textField as? CustomInputTextField, textField.customInput != nil {
709786
let customInput = textField.customInput
710787
textField.customInput = nil
711788
textField.resignFirstResponder()
@@ -723,7 +800,7 @@ extension CarbEntryViewController: EmojiInputControllerDelegate {
723800
// only adjust the absorption time if it wasn't already set.
724801
absorptionTime = orderedAbsorptionTimes[section]
725802

726-
if let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.absorptionTime.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayAccurateCarbEntryWarning))) as? DateAndDurationTableViewCell {
803+
if let cell = tableView.cellForRow(at: IndexPath(row: DetailsRow.absorptionTime.rawValue, section: Sections.indexForDetailsSection(displayWarningSection: shouldDisplayWarning))) as? DateAndDurationTableViewCell {
727804
cell.duration = orderedAbsorptionTimes[section]
728805
}
729806
}

0 commit comments

Comments
 (0)