BolusInputView.swift 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import Foundation
  2. import SwiftUI
  3. import WatchKit
  4. // MARK: - Bolus Input View
  5. struct BolusInputView: View {
  6. @Environment(\.dismiss) var dismiss
  7. @State private var bolusAmount = 0.0
  8. @State private var showingConfirmation = false
  9. @State private var confirmationProgress = 0.0
  10. let state: WatchState
  11. @FocusState private var isCrownFocused: Bool
  12. var body: some View {
  13. if showingConfirmation {
  14. BolusConfirmationView(
  15. bolusAmount: bolusAmount,
  16. progress: $confirmationProgress,
  17. state: state,
  18. dismiss: dismiss
  19. )
  20. .navigationTitle("Confirm")
  21. .navigationBarBackButtonHidden(true)
  22. .toolbar {
  23. ToolbarItem(placement: .topBarLeading) {
  24. Button {
  25. if state.carbsAmount > 0 {
  26. state.carbsAmount = 0 // reset carbs in state
  27. }
  28. showingConfirmation.toggle()
  29. } label: {
  30. Image(systemName: "xmark")
  31. }
  32. .buttonStyle(.bordered)
  33. .clipShape(Circle())
  34. }
  35. ToolbarItem(placement: .topBarTrailing) {
  36. Image(systemName: "digitalcrown.arrow.counterclockwise.fill")
  37. .symbolRenderingMode(.hierarchical)
  38. .foregroundStyle(Color.white)
  39. }
  40. }
  41. } else {
  42. VStack {
  43. if state.carbsAmount > 0 {
  44. HStack {
  45. Text("Carbs:").bold().font(.subheadline).padding(.leading)
  46. Text(String(format: "%.0f g", state.carbsAmount)).font(.subheadline).foregroundStyle(Color.orange)
  47. Spacer()
  48. }
  49. }
  50. Spacer()
  51. HStack {
  52. // "-" Button
  53. Button(action: {
  54. if bolusAmount > 0 { bolusAmount -= 1 }
  55. }) {
  56. Image(systemName: "minus.circle.fill")
  57. .font(.title3)
  58. .foregroundColor(.blue)
  59. }
  60. .buttonStyle(.borderless)
  61. .disabled(bolusAmount < 1)
  62. Spacer()
  63. // Display the current carb amount
  64. Text(String(format: "%.2f U", bolusAmount))
  65. .fontWeight(.bold)
  66. .font(.system(.title2, design: .rounded))
  67. .foregroundColor(.primary)
  68. .focusable(true)
  69. .focused($isCrownFocused)
  70. .digitalCrownRotation(
  71. $bolusAmount,
  72. from: 0,
  73. through: 150.0, // TODO: use maxBolus here
  74. by: 1, // TODO: use pump increment here
  75. sensitivity: .medium,
  76. isContinuous: false,
  77. isHapticFeedbackEnabled: true
  78. )
  79. Spacer()
  80. // TODO: introduce maxBolus here, disable button if bolusAmount > maxBolus
  81. // "+" Button
  82. Button(action: {
  83. bolusAmount += 1
  84. }) {
  85. Image(systemName: "plus.circle.fill")
  86. .font(.title3)
  87. .foregroundColor(.blue)
  88. }
  89. .buttonStyle(.borderless)
  90. }.padding(.horizontal)
  91. Text("Insulin")
  92. .font(.subheadline)
  93. .foregroundColor(.secondary)
  94. .padding(.bottom)
  95. Spacer()
  96. Button("Log Bolus") {
  97. showingConfirmation = true
  98. }
  99. .buttonStyle(.bordered)
  100. .tint(.blue)
  101. .disabled(!(bolusAmount > 0.0))
  102. }
  103. .toolbar {
  104. ToolbarItem(placement: .topBarTrailing) {
  105. Image(systemName: "syringe.fill")
  106. .resizable()
  107. .aspectRatio(contentMode: .fit)
  108. .frame(width: 14, height: 14)
  109. .padding()
  110. .background(Color.blue)
  111. .foregroundStyle(.white)
  112. .clipShape(Circle())
  113. }
  114. }
  115. }
  116. }
  117. }
  118. struct BolusConfirmationView: View {
  119. let bolusAmount: Double
  120. @Binding var progress: Double
  121. let state: WatchState
  122. let dismiss: DismissAction
  123. @FocusState private var isCrownFocused: Bool
  124. var body: some View {
  125. VStack(spacing: 10) {
  126. if state.carbsAmount > 0 {
  127. HStack {
  128. Text("Carbs:")
  129. Spacer()
  130. Text(String(format: "%.1f g", state.carbsAmount))
  131. .bold()
  132. .foregroundStyle(.orange)
  133. }.padding(.horizontal)
  134. }
  135. HStack {
  136. Text("Bolus")
  137. Spacer()
  138. Text(String(format: "%.1f U", bolusAmount))
  139. .bold()
  140. .foregroundStyle(.blue)
  141. }.padding(.horizontal)
  142. ProgressView(value: progress, total: 1.0)
  143. .tint(progress >= 1.0 ? .green : .gray)
  144. .padding(.horizontal)
  145. }
  146. .focusable(true)
  147. .focused($isCrownFocused)
  148. .digitalCrownRotation(
  149. $progress,
  150. from: 0.0,
  151. through: 1.0,
  152. by: 0.05,
  153. sensitivity: .medium,
  154. isContinuous: false,
  155. isHapticFeedbackEnabled: true
  156. )
  157. .onAppear {
  158. isCrownFocused = true
  159. }
  160. .onChange(of: progress) { _, newValue in
  161. if newValue >= 1.0 {
  162. WKInterfaceDevice.current().play(.success)
  163. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  164. if state.carbsAmount > 0 {
  165. state.sendCarbsRequest(state.carbsAmount, Date())
  166. state.carbsAmount = 0 // reset carbs in state
  167. }
  168. state.sendBolusRequest(Decimal(bolusAmount))
  169. dismiss()
  170. }
  171. } else if newValue > 0 {
  172. WKInterfaceDevice.current().play(.click)
  173. }
  174. }
  175. }
  176. }
  177. #Preview {
  178. BolusInputView(state: WatchState())
  179. }