BolusInputView.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import Foundation
  2. import SwiftUI
  3. import WatchKit
  4. // MARK: - Bolus Input View
  5. struct BolusInputView: View {
  6. @Binding var navigationPath: NavigationPath
  7. @State private var bolusAmount = 0.0
  8. let state: WatchState
  9. @FocusState private var isCrownFocused: Bool
  10. private var effectiveBolusLimit: Double {
  11. Double(truncating: state.maxBolus as NSNumber)
  12. }
  13. var trioBackgroundColor = LinearGradient(
  14. gradient: Gradient(colors: [Color.bgDarkBlue, Color.bgDarkerDarkBlue]),
  15. startPoint: .top,
  16. endPoint: .bottom
  17. )
  18. var body: some View {
  19. VStack {
  20. if state.showBolusCalculationProgress {
  21. ProgressView("Calculating Bolus...")
  22. Spacer()
  23. } else {
  24. if effectiveBolusLimit <= 0 {
  25. VStack(spacing: 8) {
  26. Text("Bolus limit cannot be fetched from phone!").font(.headline)
  27. Text("Check device settings, connect to phone, and try again.").font(.caption)
  28. }
  29. .scenePadding()
  30. } else {
  31. if state.carbsAmount > 0 {
  32. // Display the current carb amount
  33. HStack {
  34. Text("Carbs:").bold().font(.subheadline).padding(.leading)
  35. Text("\(state.carbsAmount) g").font(.subheadline).foregroundStyle(Color.orange)
  36. Spacer()
  37. }
  38. }
  39. Spacer()
  40. HStack {
  41. // "-" Button
  42. Button(action: {
  43. if bolusAmount > 0 { bolusAmount -= Double(truncating: state.bolusIncrement as NSNumber) }
  44. }) {
  45. Image(systemName: "minus.circle.fill")
  46. .font(.title3)
  47. .tint(Color.insulin)
  48. }
  49. .buttonStyle(.borderless)
  50. .disabled(bolusAmount <= 0)
  51. Spacer()
  52. let bolusIncrement = Double(truncating: state.bolusIncrement as NSNumber)
  53. let adjustedBolusAmount = floor(bolusAmount / bolusIncrement) * bolusIncrement
  54. Text(String(format: "%.2f U", adjustedBolusAmount))
  55. .fontWeight(.bold)
  56. .font(.system(.title2, design: .rounded))
  57. .foregroundColor(bolusAmount > 0.0 && bolusAmount >= effectiveBolusLimit ? .loopRed : .primary)
  58. .focusable(true)
  59. .focused($isCrownFocused)
  60. .digitalCrownRotation(
  61. $bolusAmount,
  62. from: 0,
  63. through: effectiveBolusLimit,
  64. by: Double(truncating: state.bolusIncrement as NSNumber),
  65. sensitivity: .medium,
  66. isContinuous: false,
  67. isHapticFeedbackEnabled: true
  68. )
  69. Spacer()
  70. // "+" Button
  71. Button(action: {
  72. bolusAmount = min(
  73. effectiveBolusLimit,
  74. bolusAmount + Double(truncating: state.bolusIncrement as NSNumber)
  75. )
  76. }) {
  77. Image(systemName: "plus.circle.fill")
  78. .font(.title3)
  79. .tint(Color.insulin)
  80. }
  81. .buttonStyle(.borderless)
  82. .disabled(bolusAmount >= effectiveBolusLimit)
  83. }.padding(.horizontal)
  84. Text("Insulin")
  85. .font(.subheadline)
  86. .foregroundColor(.secondary)
  87. .padding(.bottom)
  88. Spacer()
  89. if bolusAmount > 0.0 && bolusAmount >= effectiveBolusLimit {
  90. Text("Bolus Limit Reached!")
  91. .font(.footnote)
  92. .foregroundColor(.loopRed)
  93. }
  94. Button("Enact Bolus") {
  95. state.bolusAmount = min(bolusAmount, effectiveBolusLimit)
  96. navigationPath.append(NavigationDestinations.bolusConfirm)
  97. }
  98. .buttonStyle(.bordered)
  99. .tint(Color.insulin)
  100. .disabled(!(bolusAmount > 0.0) || bolusAmount > effectiveBolusLimit)
  101. Text(String(format: "Recommended: %.1f U", NSDecimalNumber(decimal: state.recommendedBolus).doubleValue))
  102. .font(.footnote)
  103. .foregroundStyle(.secondary)
  104. }
  105. }
  106. }
  107. .background(trioBackgroundColor)
  108. .toolbar {
  109. ToolbarItem(placement: .topBarTrailing) {
  110. Image(systemName: "syringe.fill")
  111. .resizable()
  112. .aspectRatio(contentMode: .fit)
  113. .frame(width: 14, height: 14)
  114. .padding()
  115. .background(Color.insulin)
  116. .foregroundStyle(.white)
  117. .clipShape(Circle())
  118. }
  119. }
  120. .blur(radius: state.showBolusProgressOverlay ? 3 : 0)
  121. .overlay {
  122. if state.showBolusProgressOverlay {
  123. BolusProgressOverlay(state: state) {
  124. state.shouldNavigateToRoot = false
  125. navigationPath.append(NavigationDestinations.acknowledgmentPending)
  126. }.transition(.opacity)
  127. }
  128. }
  129. .onAppear {
  130. // Set initial bolus amount to recommended value
  131. // Only do this if user has not updated amount previously, e.g., when navigating to next and then back to this view
  132. if bolusAmount == 0 {
  133. state.requestBolusRecommendation()
  134. bolusAmount = Double(truncating: NSDecimalNumber(decimal: state.recommendedBolus))
  135. }
  136. }
  137. // Add onChange to update bolus amount when recommendation changes
  138. .onChange(of: state.recommendedBolus) { oldValue, newValue in
  139. // Only update if user hasn't modified the value OR if recommendation hasn't changed
  140. if bolusAmount == 0 || oldValue != newValue {
  141. bolusAmount = Double(truncating: NSDecimalNumber(decimal: newValue))
  142. }
  143. }
  144. }
  145. }