|
@@ -32,6 +32,11 @@ extension Home {
|
|
|
@State var isMenuPresented = false
|
|
@State var isMenuPresented = false
|
|
|
@State var showTreatments = false
|
|
@State var showTreatments = false
|
|
|
@State var selectedTab: Int = 0
|
|
@State var selectedTab: Int = 0
|
|
|
|
|
+ @State var showQuickBolusPicker = false
|
|
|
|
|
+ @State var quickBolusAmount: Decimal = 0
|
|
|
|
|
+ @State var showQuickBolusConfirm = false
|
|
|
|
|
+ @State var showQuickBolusNoHistory = false
|
|
|
|
|
+ @State var showQuickBolusInfo = false
|
|
|
@State var showPumpSelection: Bool = false
|
|
@State var showPumpSelection: Bool = false
|
|
|
@State var showCGMSelection: Bool = false
|
|
@State var showCGMSelection: Bool = false
|
|
|
@State var notificationsDisabled = false
|
|
@State var notificationsDisabled = false
|
|
@@ -139,18 +144,18 @@ extension Home {
|
|
|
cgmSensorExpiresAt: state.cgmSensorExpiresAt,
|
|
cgmSensorExpiresAt: state.cgmSensorExpiresAt,
|
|
|
cgmWarmupEndsAt: state.cgmWarmupEndsAt
|
|
cgmWarmupEndsAt: state.cgmWarmupEndsAt
|
|
|
)
|
|
)
|
|
|
- .onTapGesture {
|
|
|
|
|
- if !state.cgmAvailable {
|
|
|
|
|
- showCGMSelection.toggle()
|
|
|
|
|
- } else {
|
|
|
|
|
- state.shouldDisplayCGMSetupSheet.toggle()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- .onLongPressGesture {
|
|
|
|
|
- let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
|
|
|
|
|
- impactHeavy.impactOccurred()
|
|
|
|
|
- state.showModal(for: .snooze)
|
|
|
|
|
|
|
+ .onTapGesture {
|
|
|
|
|
+ if !state.cgmAvailable {
|
|
|
|
|
+ showCGMSelection.toggle()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ state.shouldDisplayCGMSetupSheet.toggle()
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+ .onLongPressGesture {
|
|
|
|
|
+ let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
|
|
|
|
|
+ impactHeavy.impactOccurred()
|
|
|
|
|
+ state.showModal(for: .snooze)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var pumpView: some View {
|
|
var pumpView: some View {
|
|
@@ -1109,17 +1114,25 @@ extension Home {
|
|
|
}
|
|
}
|
|
|
.tint(Color.tabBar)
|
|
.tint(Color.tabBar)
|
|
|
|
|
|
|
|
- Button(
|
|
|
|
|
- action: {
|
|
|
|
|
- state.showModal(for: .treatmentView) },
|
|
|
|
|
- label: {
|
|
|
|
|
- Image(systemName: "plus.circle.fill")
|
|
|
|
|
- .font(.system(size: 40))
|
|
|
|
|
- .foregroundStyle(Color.tabBar)
|
|
|
|
|
- .padding(.vertical, 2)
|
|
|
|
|
- .padding(.horizontal, 24)
|
|
|
|
|
|
|
+ Image(systemName: "plus.circle.fill")
|
|
|
|
|
+ .font(.system(size: 40))
|
|
|
|
|
+ .foregroundStyle(Color.tabBar)
|
|
|
|
|
+ .padding(.vertical, 2)
|
|
|
|
|
+ .padding(.horizontal, 24)
|
|
|
|
|
+ .contentShape(Rectangle())
|
|
|
|
|
+ .onTapGesture {
|
|
|
|
|
+ state.showModal(for: .treatmentView)
|
|
|
|
|
+ }
|
|
|
|
|
+ .onLongPressGesture(minimumDuration: 0.5) {
|
|
|
|
|
+ Task {
|
|
|
|
|
+ await state.loadQuickBolusSuggestions()
|
|
|
|
|
+ if state.quickBolusHistory.isEmpty {
|
|
|
|
|
+ showQuickBolusNoHistory = true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showQuickBolusPicker = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
}.ignoresSafeArea(.keyboard, edges: .bottom).blur(radius: state.waitForSuggestion ? 8 : 0)
|
|
}.ignoresSafeArea(.keyboard, edges: .bottom).blur(radius: state.waitForSuggestion ? 8 : 0)
|
|
|
.onChange(of: selectedTab) {
|
|
.onChange(of: selectedTab) {
|
|
|
if !settingsPath.isEmpty {
|
|
if !settingsPath.isEmpty {
|
|
@@ -1128,6 +1141,19 @@ extension Home {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private func formatBolusLabel(_ amount: Decimal) -> String {
|
|
|
|
|
+ let formatted = Formatter.bolusFormatter.string(from: amount as NSDecimalNumber) ?? amount.description
|
|
|
|
|
+ return "\(formatted) U"
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func formatBolusConfirmTitle(_ amount: Decimal) -> String {
|
|
|
|
|
+ let formatted = Formatter.bolusFormatter.string(from: amount as NSDecimalNumber) ?? amount.description
|
|
|
|
|
+ return String(
|
|
|
|
|
+ format: String(localized: "Deliver %@ U?", comment: "Quick bolus confirmation dialog title; %@ is the formatted bolus amount"),
|
|
|
|
|
+ formatted
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
var body: some View {
|
|
var body: some View {
|
|
|
ZStack(alignment: .center) {
|
|
ZStack(alignment: .center) {
|
|
|
tabBar()
|
|
tabBar()
|
|
@@ -1136,6 +1162,59 @@ extension Home {
|
|
|
CustomProgressView(text: String(localized: "Updating IOB...", comment: "Progress text when updating IOB"))
|
|
CustomProgressView(text: String(localized: "Updating IOB...", comment: "Progress text when updating IOB"))
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ .confirmationDialog(
|
|
|
|
|
+ String(localized: "Quick Bolus", comment: "Title of the quick bolus picker sheet"),
|
|
|
|
|
+ isPresented: $showQuickBolusPicker,
|
|
|
|
|
+ titleVisibility: .visible
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Button(String(localized: "How does this work?", comment: "Quick bolus info button")) {
|
|
|
|
|
+ showQuickBolusInfo = true
|
|
|
|
|
+ }
|
|
|
|
|
+ ForEach(state.quickBolusHistory, id: \.self) { amount in
|
|
|
|
|
+ Button(formatBolusLabel(amount)) {
|
|
|
|
|
+ quickBolusAmount = amount
|
|
|
|
|
+ showQuickBolusConfirm = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Button(String(localized: "Cancel"), role: .cancel) {}
|
|
|
|
|
+ } message: {
|
|
|
|
|
+ Text(String(
|
|
|
|
|
+ localized: "Based on your bolus history at this time of day",
|
|
|
|
|
+ comment: "Subtitle of the quick bolus picker sheet"
|
|
|
|
|
+ ))
|
|
|
|
|
+ }
|
|
|
|
|
+ .confirmationDialog(
|
|
|
|
|
+ formatBolusConfirmTitle(quickBolusAmount),
|
|
|
|
|
+ isPresented: $showQuickBolusConfirm,
|
|
|
|
|
+ titleVisibility: .visible
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Button(String(localized: "Deliver", comment: "Quick bolus confirm button"), role: .destructive) {
|
|
|
|
|
+ Task { await state.enactQuickBolus(amount: quickBolusAmount) }
|
|
|
|
|
+ }
|
|
|
|
|
+ Button(String(localized: "Cancel"), role: .cancel) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ .alert(
|
|
|
|
|
+ String(localized: "No Quick Bolus History Yet", comment: "Alert title when no quick bolus history exists"),
|
|
|
|
|
+ isPresented: $showQuickBolusNoHistory
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Button(String(localized: "OK"), role: .cancel) {}
|
|
|
|
|
+ } message: {
|
|
|
|
|
+ Text(String(
|
|
|
|
|
+ localized: "Quick Bolus learns from your manual boluses over time. Once you've delivered a few boluses, it will suggest amounts based on what you typically take at this time of day.",
|
|
|
|
|
+ comment: "Alert body explaining that quick bolus history is empty"
|
|
|
|
|
+ ))
|
|
|
|
|
+ }
|
|
|
|
|
+ .alert(
|
|
|
|
|
+ String(localized: "About Quick Bolus", comment: "Info alert title for quick bolus feature"),
|
|
|
|
|
+ isPresented: $showQuickBolusInfo
|
|
|
|
|
+ ) {
|
|
|
|
|
+ Button(String(localized: "OK"), role: .cancel) {}
|
|
|
|
|
+ } message: {
|
|
|
|
|
+ Text(String(
|
|
|
|
|
+ localized: "Quick Bolus looks at your manual boluses from the past 90 days and suggests the amounts you most commonly take at this time of day.\n\nIt gives more weight to boluses from similar times of day, and treats weekdays and weekends separately. Older entries gradually count less.\n\nTap a suggestion to confirm and deliver it — your normal Face ID or Touch ID approval always applies.",
|
|
|
|
|
+ comment: "Info alert body explaining how quick bolus scoring works"
|
|
|
|
|
+ ))
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|