BolusInputView.swift 6.8 KB

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