BolusInputView.swift 6.9 KB

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