Skip to content

Commit 80d9168

Browse files
committed
Merge PR #603: Quick-Pick Boluses and Meals for remote commands
# Conflicts: # LoopFollow.xcodeproj/project.pbxproj
2 parents 7b32580 + 74129ae commit 80d9168

11 files changed

Lines changed: 622 additions & 0 deletions

File tree

LoopFollow.xcodeproj/project.pbxproj

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
B500000000000000000000A2 /* RemoteBolusHistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500000000000000000000A1 /* RemoteBolusHistoryEntry.swift */; };
11+
B500000000000000000000A4 /* QuickPickBolusesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500000000000000000000A3 /* QuickPickBolusesManager.swift */; };
12+
B500000000000000000000B2 /* RemoteMealHistoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500000000000000000000B1 /* RemoteMealHistoryEntry.swift */; };
13+
B500000000000000000000B4 /* QuickPickMealsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500000000000000000000B3 /* QuickPickMealsManager.swift */; };
1014
2D8068C66833EEAED7B4BEB8 /* FutureCarbsCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBAB9EECE7095238A558060 /* FutureCarbsCondition.swift */; };
1115
374A77992F5BD8B200E96858 /* APNSClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374A77982F5BD8AB00E96858 /* APNSClient.swift */; };
1216
374A77A52F5BE17000E96858 /* AppGroupID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374A779F2F5BE17000E96858 /* AppGroupID.swift */; };
@@ -97,6 +101,7 @@
97101
DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF0C2C98485400FB655A /* SecureStorageValue.swift */; };
98102
DD16AF0F2C99592F00FB655A /* HKQuantityInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */; };
99103
DD16AF112C997B4600FB655A /* LoadingButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD16AF102C997B4600FB655A /* LoadingButtonView.swift */; };
104+
B500000000000000000000C2 /* QuickPickSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B500000000000000000000C1 /* QuickPickSectionHeader.swift */; };
100105
DD1D52B92E1EB5DC00432050 /* TabPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1D52B82E1EB5DC00432050 /* TabPosition.swift */; };
101106
CC3D4E5F6A7B8C9D0E2F2A3B /* MoreMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC3D4E5F6A7B8C9D0E1F2A3B /* MoreMenuView.swift */; };
102107
DD7A3B5D2F1E8D9A00B4C6E1 /* BGDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7A3B5C2F1E8D9A00B4C6E1 /* BGDisplayView.swift */; };
@@ -465,6 +470,10 @@
465470
/* End PBXCopyFilesBuildPhase section */
466471

467472
/* Begin PBXFileReference section */
473+
B500000000000000000000A1 /* RemoteBolusHistoryEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteBolusHistoryEntry.swift; sourceTree = "<group>"; };
474+
B500000000000000000000A3 /* QuickPickBolusesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickBolusesManager.swift; sourceTree = "<group>"; };
475+
B500000000000000000000B1 /* RemoteMealHistoryEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteMealHistoryEntry.swift; sourceTree = "<group>"; };
476+
B500000000000000000000B3 /* QuickPickMealsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickMealsManager.swift; sourceTree = "<group>"; };
468477
059B0FA59AABFE72FE13DDDA /* Pods-LoopFollow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LoopFollow.release.xcconfig"; path = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow.release.xcconfig"; sourceTree = "<group>"; };
469478
2B9BEC26E4E48EF9B811A372 /* PendingFutureCarb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingFutureCarb.swift; sourceTree = "<group>"; };
470479
2EBAB9EECE7095238A558060 /* FutureCarbsCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureCarbsCondition.swift; sourceTree = "<group>"; };
@@ -552,6 +561,7 @@
552561
DD16AF0C2C98485400FB655A /* SecureStorageValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStorageValue.swift; sourceTree = "<group>"; };
553562
DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKQuantityInputView.swift; sourceTree = "<group>"; };
554563
DD16AF102C997B4600FB655A /* LoadingButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingButtonView.swift; sourceTree = "<group>"; };
564+
B500000000000000000000C1 /* QuickPickSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickPickSectionHeader.swift; sourceTree = "<group>"; };
555565
DD1D52B82E1EB5DC00432050 /* TabPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabPosition.swift; sourceTree = "<group>"; };
556566
CC3D4E5F6A7B8C9D0E1F2A3B /* MoreMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreMenuView.swift; sourceTree = "<group>"; };
557567
DD7A3B5C2F1E8D9A00B4C6E1 /* BGDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGDisplayView.swift; sourceTree = "<group>"; };
@@ -1046,9 +1056,29 @@
10461056
path = Metric;
10471057
sourceTree = "<group>";
10481058
};
1059+
B500000000000000000000B5 /* QuickPickMeals */ = {
1060+
isa = PBXGroup;
1061+
children = (
1062+
B500000000000000000000B1 /* RemoteMealHistoryEntry.swift */,
1063+
B500000000000000000000B3 /* QuickPickMealsManager.swift */,
1064+
);
1065+
path = QuickPickMeals;
1066+
sourceTree = "<group>";
1067+
};
1068+
B500000000000000000000A5 /* QuickPickBoluses */ = {
1069+
isa = PBXGroup;
1070+
children = (
1071+
B500000000000000000000A1 /* RemoteBolusHistoryEntry.swift */,
1072+
B500000000000000000000A3 /* QuickPickBolusesManager.swift */,
1073+
);
1074+
path = QuickPickBoluses;
1075+
sourceTree = "<group>";
1076+
};
10491077
DD0C0C6E2C4AFFB800DBADDF /* Remote */ = {
10501078
isa = PBXGroup;
10511079
children = (
1080+
B500000000000000000000A5 /* QuickPickBoluses */,
1081+
B500000000000000000000B5 /* QuickPickMeals */,
10521082
DDEF503E2D479B8A00884336 /* LoopAPNS */,
10531083
DD4878112C7B74F90048F05C /* TRC */,
10541084
DD4878062C7B2E9E0048F05C /* Settings */,
@@ -1394,6 +1424,7 @@
13941424
DDF6999D2C5AAA640058A8D9 /* ErrorMessageView.swift */,
13951425
DD16AF0E2C99592F00FB655A /* HKQuantityInputView.swift */,
13961426
DD16AF102C997B4600FB655A /* LoadingButtonView.swift */,
1427+
B500000000000000000000C1 /* QuickPickSectionHeader.swift */,
13971428
DDE75D262DE5E539007C1FC1 /* ActionRow.swift */,
13981429
654132E62E19EA7E00BDBE08 /* SimpleQRCodeScannerView.swift */,
13991430
DDE75D282DE5E56C007C1FC1 /* LinkRow.swift */,
@@ -2168,6 +2199,7 @@
21682199
DDC6CA4B2DD8E4960060EE25 /* PumpVolumeAlarmEditor.swift in Sources */,
21692200
6589CC752E9EAFB700BB18FE /* SettingsMigrationManager.swift in Sources */,
21702201
DD16AF0F2C99592F00FB655A /* HKQuantityInputView.swift in Sources */,
2202+
B500000000000000000000C2 /* QuickPickSectionHeader.swift in Sources */,
21712203
DDFF3D7F2D1414A200BF9D9E /* BLEDevice.swift in Sources */,
21722204
DD9ACA042D32821400415D8A /* DeviceStatusTask.swift in Sources */,
21732205
FC16A97D24996747003D6245 /* SpeakBG.swift in Sources */,
@@ -2194,6 +2226,10 @@
21942226
6584B1012E4A263900135D4D /* TOTPService.swift in Sources */,
21952227
DD48780E2C7B74A40048F05C /* TrioRemoteControlViewModel.swift in Sources */,
21962228
DDEF503A2D31615000999A5D /* LogManager.swift in Sources */,
2229+
B500000000000000000000A2 /* RemoteBolusHistoryEntry.swift in Sources */,
2230+
B500000000000000000000A4 /* QuickPickBolusesManager.swift in Sources */,
2231+
B500000000000000000000B2 /* RemoteMealHistoryEntry.swift in Sources */,
2232+
B500000000000000000000B4 /* QuickPickMealsManager.swift in Sources */,
21972233
DD4878172C7B75350048F05C /* BolusView.swift in Sources */,
21982234
DD026E592EA2C8A200A39CB5 /* InsulinPrecisionManager.swift in Sources */,
21992235
374A77992F5BD8B200E96858 /* APNSClient.swift in Sources */,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// LoopFollow
2+
// QuickPickSectionHeader.swift
3+
4+
import SwiftUI
5+
6+
struct QuickPickSectionHeader: View {
7+
let title: String
8+
let infoText: String
9+
@State private var showInfo = false
10+
11+
var body: some View {
12+
HStack(spacing: 4) {
13+
Text(title)
14+
Button {
15+
showInfo = true
16+
} label: {
17+
Image(systemName: "info.circle")
18+
.foregroundStyle(Color.accentColor)
19+
}
20+
.buttonStyle(.plain)
21+
}
22+
.sheet(isPresented: $showInfo) {
23+
QuickPickInfoSheet(title: title, text: infoText)
24+
}
25+
}
26+
}
27+
28+
private struct QuickPickInfoSheet: View {
29+
let title: String
30+
let text: String
31+
@Environment(\.dismiss) private var dismiss
32+
33+
var body: some View {
34+
NavigationStack {
35+
ScrollView {
36+
Text(text)
37+
.padding()
38+
.frame(maxWidth: .infinity, alignment: .leading)
39+
}
40+
.navigationTitle(title)
41+
.navigationBarTitleDisplayMode(.inline)
42+
.toolbar {
43+
ToolbarItem(placement: .confirmationAction) {
44+
Button("Done") { dismiss() }
45+
}
46+
}
47+
}
48+
.presentationDetents([.medium])
49+
.presentationDragIndicator(.visible)
50+
}
51+
}
52+
53+
extension QuickPickSectionHeader {
54+
static let bolusInfoText = """
55+
These buttons show your most-used recent bolus amounts.
56+
57+
They're based on what you've sent before at similar times on similar days — so if you usually give 4 units before breakfast on weekdays, that button will show up on weekday mornings.
58+
59+
Tap a button to fill in the amount. Nothing is sent until you review and confirm.
60+
"""
61+
62+
static let mealInfoText = """
63+
These buttons show your most-used recent meals.
64+
65+
They're based on what you've sent before at similar times on similar days — so if you usually send the same breakfast on weekday mornings, it'll appear as an option.
66+
67+
Tap a button to fill in the details. Nothing is sent until you review and confirm.
68+
"""
69+
}

LoopFollow/Remote/LoopAPNS/LoopAPNSBolusView.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct LoopAPNSBolusView: View {
1313
@State private var alertMessage = ""
1414
@State private var alertType: AlertType = .success
1515

16+
@ObservedObject private var quickPickBoluses = QuickPickBolusesManager.shared
1617
@FocusState private var insulinFieldIsFocused: Bool
1718

1819
// Add state for recommended bolus and warning
@@ -72,6 +73,30 @@ struct LoopAPNSBolusView: View {
7273
}
7374
}
7475

76+
if !quickPickBoluses.quickPickBoluses.isEmpty {
77+
Section(header: QuickPickSectionHeader(title: "Quick-Pick Boluses", infoText: QuickPickSectionHeader.bolusInfoText)) {
78+
ScrollView(.horizontal, showsIndicators: false) {
79+
HStack(spacing: 12) {
80+
ForEach(quickPickBoluses.quickPickBoluses) { bolus in
81+
Button {
82+
insulinAmount = HKQuantity(unit: .internationalUnit(), doubleValue: bolus.units)
83+
} label: {
84+
Text("\(InsulinFormatter.shared.string(bolus.units))U")
85+
.font(.subheadline.weight(.medium))
86+
.padding(.horizontal, 14)
87+
.padding(.vertical, 8)
88+
.background(Color.accentColor.opacity(0.15))
89+
.foregroundColor(.accentColor)
90+
.cornerRadius(8)
91+
}
92+
.buttonStyle(.plain)
93+
}
94+
}
95+
.padding(.vertical, 4)
96+
}
97+
}
98+
}
99+
75100
Section {
76101
HKQuantityInputView(
77102
label: "Insulin Amount",
@@ -186,6 +211,10 @@ struct LoopAPNSBolusView: View {
186211
showAlert = true
187212
}
188213

214+
let step = Storage.shared.bolusIncrement.value.doubleValue(for: .internationalUnit())
215+
let maxBolus = Storage.shared.maxBolus.value.doubleValue(for: .internationalUnit())
216+
quickPickBoluses.refresh(stepIncrement: max(0.001, step), maxBolus: maxBolus)
217+
189218
loadRecommendedBolus()
190219
// Reset timer state so it shows '-' until first tick
191220
otpTimeRemaining = nil
@@ -374,6 +403,10 @@ struct LoopAPNSBolusView: View {
374403
DispatchQueue.main.async {
375404
self.isLoading = false
376405
if success {
406+
let sentUnits = insulinAmount.doubleValue(for: .internationalUnit())
407+
if sentUnits > 0 {
408+
QuickPickBolusesManager.shared.recordBolus(units: sentUnits)
409+
}
377410
// Mark TOTP code as used
378411
TOTPService.shared.markTOTPAsUsed(qrCodeURL: Storage.shared.loopAPNSQrCodeURL.value)
379412
self.alertMessage = "Insulin sent successfully!"

LoopFollow/Remote/LoopAPNS/LoopAPNSCarbsView.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct LoopAPNSCarbsView: View {
88
private typealias AbsorptionPreset = (hours: Int, minutes: Int)
99

1010
@Environment(\.presentationMode) var presentationMode
11+
@ObservedObject private var quickPickMeals = QuickPickMealsManager.shared
1112
@State private var carbsAmount = HKQuantity(unit: .gram(), doubleValue: 0.0)
1213
@State private var absorptionHours = 3
1314
@State private var absorptionMinutes = 0
@@ -102,6 +103,30 @@ struct LoopAPNSCarbsView: View {
102103
NavigationView {
103104
VStack {
104105
Form {
106+
if !quickPickMeals.quickPickMeals.isEmpty {
107+
Section(header: QuickPickSectionHeader(title: "Quick-Pick Meals", infoText: QuickPickSectionHeader.mealInfoText)) {
108+
ScrollView(.horizontal, showsIndicators: false) {
109+
HStack(spacing: 12) {
110+
ForEach(quickPickMeals.quickPickMeals) { meal in
111+
Button {
112+
carbsAmount = HKQuantity(unit: .gram(), doubleValue: meal.carbs)
113+
} label: {
114+
Text("\(Int(meal.carbs))g")
115+
.font(.subheadline.weight(.medium))
116+
.padding(.horizontal, 14)
117+
.padding(.vertical, 8)
118+
.background(Color.accentColor.opacity(0.15))
119+
.foregroundColor(.accentColor)
120+
.cornerRadius(8)
121+
}
122+
.buttonStyle(.plain)
123+
}
124+
}
125+
.padding(.vertical, 4)
126+
}
127+
}
128+
}
129+
105130
Section {
106131
HKQuantityInputView(
107132
label: "Carbs Amount",
@@ -384,6 +409,12 @@ struct LoopAPNSCarbsView: View {
384409
alertType = .error
385410
showAlert = true
386411
}
412+
413+
quickPickMeals.refresh(
414+
maxCarbs: Storage.shared.maxCarbs.value.doubleValue(for: .gram()),
415+
includeFatProtein: false
416+
)
417+
387418
// Reset timer state so it shows '-' until first tick
388419
otpTimeRemaining = nil
389420
// Don't reset TOTP usage flag here - let the timer handle it
@@ -534,6 +565,10 @@ struct LoopAPNSCarbsView: View {
534565
DispatchQueue.main.async {
535566
self.isLoading = false
536567
if success {
568+
let sentCarbs = carbsAmount.doubleValue(for: .gram())
569+
if sentCarbs > 0 {
570+
QuickPickMealsManager.shared.recordMeal(carbs: sentCarbs)
571+
}
537572
// Mark TOTP code as used
538573
TOTPService.shared.markTOTPAsUsed(qrCodeURL: Storage.shared.loopAPNSQrCodeURL.value)
539574
let timeFormatter = DateFormatter()

0 commit comments

Comments
 (0)