BolusInputView.swift 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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(String(
  22. localized: "Calculating Bolus...",
  23. comment: "Progress view text on watch when calculating bolus"
  24. ))
  25. Spacer()
  26. } else {
  27. if effectiveBolusLimit <= 0 {
  28. VStack(spacing: 8) {
  29. Text("Bolus limit cannot be fetched from phone!").font(.headline)
  30. Text("Check device settings, connect to phone, and try again.").font(.caption)
  31. }
  32. .scenePadding()
  33. } else {
  34. if state.carbsAmount > 0 {
  35. // Display the current carb amount
  36. HStack {
  37. Text("Carbs:").bold().font(.subheadline).padding(.leading)
  38. Text("\(state.carbsAmount) g").font(.subheadline).foregroundStyle(Color.orange)
  39. Spacer()
  40. }
  41. }
  42. Spacer()
  43. HStack {
  44. // "-" Button
  45. Button(action: {
  46. if bolusAmount > 0 { bolusAmount -= Double(truncating: state.bolusIncrement as NSNumber) }
  47. }) {
  48. Image(systemName: "minus.circle.fill")
  49. .font(.title3)
  50. .tint(Color.insulin)
  51. }
  52. .buttonStyle(.borderless)
  53. .disabled(bolusAmount <= 0)
  54. Spacer()
  55. let bolusIncrement = Double(truncating: state.bolusIncrement as NSNumber)
  56. let adjustedBolusAmount = floor(bolusAmount / bolusIncrement) * bolusIncrement
  57. Text(String(format: "%.2f \(String(localized: "U", comment: "Insulin unit"))", adjustedBolusAmount))
  58. .fontWeight(.bold)
  59. .font(.system(.title2, design: .rounded))
  60. .foregroundColor(bolusAmount > 0.0 && bolusAmount >= effectiveBolusLimit ? .loopRed : .primary)
  61. .focusable(true)
  62. .focused($isCrownFocused)
  63. .digitalCrownRotation(
  64. $bolusAmount,
  65. from: 0,
  66. through: effectiveBolusLimit,
  67. by: Double(truncating: state.bolusIncrement as NSNumber),
  68. sensitivity: .medium,
  69. isContinuous: false,
  70. isHapticFeedbackEnabled: true
  71. )
  72. Spacer()
  73. // "+" Button
  74. Button(action: {
  75. bolusAmount = min(
  76. effectiveBolusLimit,
  77. bolusAmount + Double(truncating: state.bolusIncrement as NSNumber)
  78. )
  79. }) {
  80. Image(systemName: "plus.circle.fill")
  81. .font(.title3)
  82. .tint(Color.insulin)
  83. }
  84. .buttonStyle(.borderless)
  85. .disabled(bolusAmount >= effectiveBolusLimit)
  86. }.padding(.horizontal)
  87. Text("Insulin")
  88. .font(.subheadline)
  89. .foregroundColor(.secondary)
  90. .padding(.bottom)
  91. Spacer()
  92. if bolusAmount > 0.0 && bolusAmount >= effectiveBolusLimit {
  93. Text("Bolus Limit Reached!")
  94. .font(.footnote)
  95. .foregroundColor(.loopRed)
  96. }
  97. Button("Enact Bolus") {
  98. state.bolusAmount = min(bolusAmount, effectiveBolusLimit)
  99. navigationPath.append(NavigationDestinations.bolusConfirm)
  100. }
  101. .buttonStyle(.bordered)
  102. .tint(Color.insulin)
  103. .disabled(!(bolusAmount > 0.0) || bolusAmount > effectiveBolusLimit)
  104. Text(String(
  105. format: "\(String(localized: "Recommended:", comment: "Recommended bolus on Watch")) %.1f \(String(localized: "U", comment: "Insulin unit"))",
  106. NSDecimalNumber(decimal: state.recommendedBolus).doubleValue
  107. ))
  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. .onAppear {
  127. // Set initial bolus amount to recommended value
  128. // Only do this if user has not updated amount previously, e.g., when navigating to next and then back to this view
  129. if bolusAmount == 0 {
  130. state.requestBolusRecommendation()
  131. bolusAmount = Double(truncating: NSDecimalNumber(decimal: state.recommendedBolus))
  132. }
  133. }
  134. // Add onChange to update bolus amount when recommendation changes
  135. .onChange(of: state.recommendedBolus) { oldValue, newValue in
  136. // Only update if user hasn't modified the value OR if recommendation hasn't changed
  137. if bolusAmount == 0 || oldValue != newValue {
  138. bolusAmount = Double(truncating: NSDecimalNumber(decimal: newValue))
  139. }
  140. }
  141. }
  142. }