Skip to content

Commit e5fd6b4

Browse files
committed
CarbEntryView DIY features: missed meal, override warning
1 parent e589d2f commit e5fd6b4

File tree

4 files changed

+149
-31
lines changed

4 files changed

+149
-31
lines changed

Loop/View Controllers/StatusTableViewController.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,9 @@ final class StatusTableViewController: LoopChartsTableViewController {
13711371
present(navigationWrapper, animated: true)
13721372
} else {
13731373
let viewModel = CarbEntryViewModel(delegate: deviceManager)
1374+
if let activity {
1375+
viewModel.restoreUserActivityState(activity)
1376+
}
13741377
let carbEntryView = CarbEntryView(viewModel: viewModel)
13751378
.environmentObject(deviceManager.displayGlucosePreference)
13761379
let hostingController = DismissibleHostingController(rootView: carbEntryView, isModalInPresentation: false)

Loop/View Models/CarbEntryViewModel.swift

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,27 @@ final class CarbEntryViewModel: ObservableObject {
2626
case warningQuantityValidation
2727
}
2828

29-
@Published var alert: CarbEntryViewModel.Alert?
29+
enum Warning: Identifiable {
30+
var id: Self {
31+
return self
32+
}
33+
34+
var priority: Int {
35+
switch self {
36+
case .entryIsMissedMeal:
37+
return 1
38+
case .overrideInProgress:
39+
return 2
40+
}
41+
}
42+
43+
case entryIsMissedMeal
44+
case overrideInProgress
45+
}
3046

47+
@Published var alert: CarbEntryViewModel.Alert?
48+
@Published var warnings: Set<Warning> = []
49+
3150
@Published var bolusViewModel: BolusEntryViewModel?
3251

3352
let shouldBeginEditingQuantity: Bool
@@ -77,6 +96,7 @@ final class CarbEntryViewModel: ObservableObject {
7796
observeAbsorptionTimeChange()
7897
observeFavoriteFoodChange()
7998
observeFavoriteFoodIndexChange()
99+
observeLoopUpdates()
80100
}
81101

82102
/// Initalizer for when`CarbEntryView` has an entry to edit
@@ -92,6 +112,8 @@ final class CarbEntryViewModel: ObservableObject {
92112
self.absorptionTimeWasEdited = true
93113
self.usesCustomFoodType = true
94114
self.shouldBeginEditingQuantity = false
115+
116+
observeLoopUpdates()
95117
}
96118

97119
var originalCarbEntry: StoredCarbEntry? = nil
@@ -235,6 +257,49 @@ final class CarbEntryViewModel: ObservableObject {
235257
}
236258

237259
// MARK: - Utility
260+
func restoreUserActivityState(_ activity: NSUserActivity) {
261+
if let entry = activity.newCarbEntry {
262+
time = entry.date
263+
carbsQuantity = entry.quantity.doubleValue(for: preferredCarbUnit)
264+
265+
if let foodType = entry.foodType {
266+
self.foodType = foodType
267+
usesCustomFoodType = true
268+
}
269+
270+
if let absorptionTime = entry.absorptionTime {
271+
self.absorptionTime = absorptionTime
272+
absorptionTimeWasEdited = true
273+
}
274+
275+
if activity.entryisMissedMeal {
276+
warnings.insert(.entryIsMissedMeal)
277+
}
278+
}
279+
}
280+
281+
private func observeLoopUpdates() {
282+
self.checkIfOverrideEnabled()
283+
NotificationCenter.default
284+
.publisher(for: .LoopDataUpdated)
285+
.receive(on: DispatchQueue.main)
286+
.sink { [weak self] _ in
287+
self?.checkIfOverrideEnabled()
288+
}
289+
.store(in: &cancellables)
290+
}
291+
292+
private func checkIfOverrideEnabled() {
293+
if let managerSettings = delegate?.settings {
294+
if let overrideSettings = managerSettings.scheduleOverride?.settings, overrideSettings.effectiveInsulinNeedsScaleFactor != 1.0 {
295+
self.warnings.insert(.overrideInProgress)
296+
}
297+
else {
298+
self.warnings.remove(.overrideInProgress)
299+
}
300+
}
301+
}
302+
238303
private func observeAbsorptionTimeChange() {
239304
$absorptionTime
240305
.receive(on: RunLoop.main)

Loop/Views/AddEditFavoriteFoodView.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ struct AddEditFavoriteFoodView: View {
8888

8989
private var card: some View {
9090
VStack(spacing: 10) {
91-
var nameFocused: Binding<Bool> = Binding(get: { expandedRow == .name }, set: { expandedRow = $0 ? .name : nil })
92-
var carbQuantityFocused: Binding<Bool> = Binding(get: { expandedRow == .carbQuantity }, set: { expandedRow = $0 ? .carbQuantity : nil })
93-
var foodTypeFocused: Binding<Bool> = Binding(get: { expandedRow == .foodType }, set: { expandedRow = $0 ? .foodType : nil })
94-
var absorptionTimeFocused: Binding<Bool> = Binding(get: { expandedRow == .absorptionTime }, set: { expandedRow = $0 ? .absorptionTime : nil })
91+
let nameFocused: Binding<Bool> = Binding(get: { expandedRow == .name }, set: { expandedRow = $0 ? .name : nil })
92+
let carbQuantityFocused: Binding<Bool> = Binding(get: { expandedRow == .carbQuantity }, set: { expandedRow = $0 ? .carbQuantity : nil })
93+
let foodTypeFocused: Binding<Bool> = Binding(get: { expandedRow == .foodType }, set: { expandedRow = $0 ? .foodType : nil })
94+
let absorptionTimeFocused: Binding<Bool> = Binding(get: { expandedRow == .absorptionTime }, set: { expandedRow = $0 ? .absorptionTime : nil })
9595

9696
TextFieldRow(text: $viewModel.name, isFocused: nameFocused, title: "Name", placeholder: "Apple")
9797

Loop/Views/CarbEntryView.swift

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ struct CarbEntryView: View, HorizontalSizeClassOverride {
6666
.edgesIgnoringSafeArea(.all)
6767

6868
ScrollView {
69+
warningsCard
70+
6971
mainCard
7072
.padding(.top, 8)
7173

@@ -95,10 +97,10 @@ struct CarbEntryView: View, HorizontalSizeClassOverride {
9597

9698
private var mainCard: some View {
9799
VStack(spacing: 10) {
98-
var amountConsumedFocused: Binding<Bool> = Binding(get: { expandedRow == .amountConsumed }, set: { expandedRow = $0 ? .amountConsumed : nil })
99-
var timeFocused: Binding<Bool> = Binding(get: { expandedRow == .time }, set: { expandedRow = $0 ? .time : nil })
100-
var foodTypeFocused: Binding<Bool> = Binding(get: { expandedRow == .foodType }, set: { expandedRow = $0 ? .foodType : nil })
101-
var absorptionTimeFocused: Binding<Bool> = Binding(get: { expandedRow == .absorptionTime }, set: { expandedRow = $0 ? .absorptionTime : nil })
100+
let amountConsumedFocused: Binding<Bool> = Binding(get: { expandedRow == .amountConsumed }, set: { expandedRow = $0 ? .amountConsumed : nil })
101+
let timeFocused: Binding<Bool> = Binding(get: { expandedRow == .time }, set: { expandedRow = $0 ? .time : nil })
102+
let foodTypeFocused: Binding<Bool> = Binding(get: { expandedRow == .foodType }, set: { expandedRow = $0 ? .foodType : nil })
103+
let absorptionTimeFocused: Binding<Bool> = Binding(get: { expandedRow == .absorptionTime }, set: { expandedRow = $0 ? .absorptionTime : nil })
102104

103105
CarbQuantityRow(quantity: $viewModel.carbsQuantity, isFocused: amountConsumedFocused, title: NSLocalizedString("Amount Consumed", comment: "Label for carb quantity entry row on carb entry screen"), preferredCarbUnit: viewModel.preferredCarbUnit)
104106

@@ -133,6 +135,49 @@ struct CarbEntryView: View, HorizontalSizeClassOverride {
133135
private func clearExpandedRow() {
134136
self.expandedRow = nil
135137
}
138+
}
139+
140+
// MARK: - Warnings & Alerts
141+
extension CarbEntryView {
142+
private var warningsCard: some View {
143+
ForEach(Array(viewModel.warnings).sorted(by: { $0.priority < $1.priority })) { warning in
144+
warningView(for: warning)
145+
.padding(.vertical, 8)
146+
.padding(.horizontal)
147+
.background(CardBackground())
148+
.padding(.horizontal)
149+
.padding(.top, 8)
150+
}
151+
}
152+
153+
private func warningView(for warning: CarbEntryViewModel.Warning) -> some View {
154+
HStack {
155+
Image(systemName: "exclamationmark.triangle.fill")
156+
.foregroundColor(triangleColor(for: warning))
157+
158+
Text(warningText(for: warning))
159+
.font(.caption)
160+
}
161+
.frame(maxWidth: .infinity, alignment: .leading)
162+
}
163+
164+
private func triangleColor(for warning: CarbEntryViewModel.Warning) -> Color {
165+
switch warning {
166+
case .entryIsMissedMeal:
167+
return .critical
168+
case .overrideInProgress:
169+
return .warning
170+
}
171+
}
172+
173+
private func warningText(for warning: CarbEntryViewModel.Warning) -> String {
174+
switch warning {
175+
case .entryIsMissedMeal:
176+
return NSLocalizedString("Loop has detected an missed meal and estimated its size. Edit the carb amount to match the amount of any carbs you may have eaten.", comment: "Warning displayed when user is adding a meal from an missed meal notification")
177+
case .overrideInProgress:
178+
return 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")
179+
}
180+
}
136181

137182
private func alert(for alert: CarbEntryViewModel.Alert) -> SwiftUI.Alert {
138183
switch alert {
@@ -162,29 +207,8 @@ struct CarbEntryView: View, HorizontalSizeClassOverride {
162207
}
163208
}
164209

210+
// MARK: - Favorite Foods Card
165211
extension CarbEntryView {
166-
private var dismissButton: some View {
167-
Button(action: dismiss) {
168-
Text("Cancel")
169-
}
170-
}
171-
172-
private var continueButton: some View {
173-
Button(action: viewModel.continueToBolus) {
174-
Text("Continue")
175-
}
176-
.disabled(viewModel.continueButtonDisabled)
177-
}
178-
179-
private var continueActionButton: some View {
180-
Button(action: viewModel.continueToBolus) {
181-
Text("Continue")
182-
}
183-
.buttonStyle(ActionButtonStyle())
184-
.padding()
185-
.disabled(viewModel.continueButtonDisabled)
186-
}
187-
188212
private var favoriteFoodsCard: some View {
189213
VStack(alignment: .leading, spacing: 6) {
190214
Text("FAVORITE FOODS")
@@ -262,6 +286,32 @@ extension CarbEntryView {
262286
}
263287
}
264288

289+
// MARK: - Other UI Elements
290+
extension CarbEntryView {
291+
private var dismissButton: some View {
292+
Button(action: dismiss) {
293+
Text("Cancel")
294+
}
295+
}
296+
297+
private var continueButton: some View {
298+
Button(action: viewModel.continueToBolus) {
299+
Text("Continue")
300+
}
301+
.disabled(viewModel.continueButtonDisabled)
302+
}
303+
304+
private var continueActionButton: some View {
305+
Button(action: viewModel.continueToBolus) {
306+
Text("Continue")
307+
}
308+
.buttonStyle(ActionButtonStyle())
309+
.padding()
310+
.disabled(viewModel.continueButtonDisabled)
311+
}
312+
313+
}
314+
265315
extension CarbEntryView {
266316
enum Row {
267317
case amountConsumed, time, foodType, absorptionTime, favoriteFoodSelection

0 commit comments

Comments
 (0)