CarbsInputView.swift 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import Foundation
  2. import SwiftUI
  3. // MARK: - Carbs Input View
  4. struct CarbsInputView: View {
  5. @ObservedObject var navigationState: NavigationState
  6. @State private var carbsAmount: Double = 0.0 // Needs to be Double due to .digitalCrownRotation() stride
  7. @State private var navigateToBolus = false // Track navigation to BolusInputView
  8. @FocusState private var isCrownFocused: Bool // Manage crown focus
  9. let state: WatchState
  10. let continueToBolus: Bool
  11. private var effectiveCarbsLimit: Double {
  12. // Extract current COB from string and convert to Double
  13. let currentCOB = Double(state.cob?.replacingOccurrences(of: " g", with: "") ?? "0") ?? 0
  14. // Calculate available COB
  15. let availableCOB = max(0, Double(truncating: state.maxCOB as NSNumber) - currentCOB)
  16. return min(
  17. Double(truncating: state.maxCarbs as NSNumber),
  18. availableCOB
  19. )
  20. }
  21. var body: some View {
  22. let buttonLabel = continueToBolus ? "Proceed" : "Log Carbs"
  23. // TODO: introduce meal setting fpu enablement to conditional handle FPU
  24. VStack {
  25. Spacer()
  26. HStack {
  27. // "-" Button
  28. Button(action: {
  29. if carbsAmount > 0 { carbsAmount -= 1 }
  30. }) {
  31. Image(systemName: "minus.circle.fill")
  32. .font(.title3)
  33. .foregroundColor(.orange)
  34. }
  35. .buttonStyle(.borderless)
  36. .disabled(carbsAmount < 1)
  37. Spacer()
  38. // Display the current carb amount
  39. Text(String(format: "%.0f g", carbsAmount))
  40. .fontWeight(.bold)
  41. .font(.system(.title2, design: .rounded))
  42. .foregroundColor(.primary)
  43. .focusable(true)
  44. .focused($isCrownFocused)
  45. .digitalCrownRotation(
  46. $carbsAmount,
  47. from: 0,
  48. through: effectiveCarbsLimit,
  49. by: 1,
  50. sensitivity: .medium,
  51. isContinuous: false,
  52. isHapticFeedbackEnabled: true
  53. )
  54. Spacer()
  55. // "+" Button
  56. Button(action: {
  57. carbsAmount += 1
  58. }) {
  59. Image(systemName: "plus.circle.fill")
  60. .font(.title3)
  61. .foregroundColor(.orange)
  62. }
  63. .buttonStyle(.borderless)
  64. .disabled(carbsAmount >= effectiveCarbsLimit)
  65. }.padding(.horizontal)
  66. Text("Carbohydrates")
  67. .font(.subheadline)
  68. .foregroundColor(.secondary)
  69. .padding(.bottom)
  70. Spacer()
  71. Button(buttonLabel) {
  72. if continueToBolus {
  73. state.carbsAmount = Int(min(carbsAmount, effectiveCarbsLimit))
  74. navigationState.path.append(NavigationDestinations.bolusInput)
  75. } else {
  76. // TODO: add a fancy success animation
  77. state.sendCarbsRequest(Int(min(carbsAmount, effectiveCarbsLimit)))
  78. navigationState.resetToRoot()
  79. }
  80. }
  81. .buttonStyle(.bordered)
  82. .tint(.orange)
  83. .disabled(!(carbsAmount > 0.0) || carbsAmount >= effectiveCarbsLimit)
  84. }
  85. .toolbar {
  86. ToolbarItem(placement: .topBarTrailing) {
  87. Image(systemName: "fork.knife")
  88. .resizable()
  89. .aspectRatio(contentMode: .fit)
  90. .frame(width: 14, height: 14)
  91. .padding()
  92. .background(Color.orange)
  93. .foregroundStyle(.white)
  94. .clipShape(Circle())
  95. }
  96. }
  97. }
  98. }