TrioMainWatchView.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import Charts
  2. import SwiftUI
  3. struct TrioMainWatchView: View {
  4. @State private var state = WatchState()
  5. // misc
  6. @State private var currentPage: Int = 0
  7. @State private var rotationDegrees: Double = 0.0
  8. @State private var showingTempTargetSheet = false
  9. // view visbility
  10. @State private var showingTreatmentMenuSheet: Bool = false
  11. @State private var showingOverrideSheet: Bool = false
  12. // navigation flag for meal bolus combo
  13. @State private var continueToBolus = false
  14. @State private var navigationPath: [NavigationDestinations] = []
  15. // treatments
  16. @State private var selectedTreatment: TreatmentOption?
  17. // Active adjustment indicator
  18. private func isAdjustmentActive<T>(for presets: [T], predicate: (T) -> Bool) -> Bool {
  19. let sortedPresets = presets.sorted { predicate($0) && !predicate($1) }
  20. return !sortedPresets.isEmpty && sortedPresets.first(where: predicate) != nil
  21. }
  22. private var isTempTargetActive: Bool {
  23. isAdjustmentActive(for: state.tempTargetPresets) { $0.isEnabled }
  24. }
  25. private var isOverrideActive: Bool {
  26. isAdjustmentActive(for: state.overridePresets) { $0.isEnabled }
  27. }
  28. private var trioBackgroundColor = LinearGradient(
  29. gradient: Gradient(colors: [Color.bgDarkBlue, Color.bgDarkerDarkBlue]),
  30. startPoint: .top,
  31. endPoint: .bottom
  32. )
  33. var body: some View {
  34. NavigationStack(path: $navigationPath) {
  35. TabView(selection: $currentPage) {
  36. // Page 1: Current glucose trend in "BG bobble"
  37. GlucoseTrendView(state: state, rotationDegrees: rotationDegrees)
  38. .tag(0)
  39. // Page 2: Glucose chart
  40. GlucoseChartView(glucoseValues: state.glucoseValues)
  41. .tag(1)
  42. }
  43. .background(trioBackgroundColor)
  44. .tabViewStyle(.verticalPage)
  45. .digitalCrownRotation($currentPage.doubleBinding(), from: 0, through: 1, by: 1)
  46. .onChange(of: state.trend) { _, newTrend in
  47. withAnimation {
  48. updateRotation(for: newTrend)
  49. }
  50. }
  51. .onAppear {
  52. state.confirmationProgress = 0 // reset auth progress
  53. }
  54. .toolbar {
  55. ToolbarItem(placement: .topBarLeading) {
  56. HStack {
  57. Image(systemName: "syringe.fill")
  58. .foregroundStyle(.blue)
  59. Text(state.iob ?? "--")
  60. .foregroundStyle(.white)
  61. }.font(.caption)
  62. }
  63. ToolbarItem(placement: .topBarTrailing) {
  64. HStack {
  65. Text(state.cob ?? "--")
  66. .foregroundStyle(.white)
  67. Image(systemName: "fork.knife")
  68. .foregroundStyle(.orange)
  69. }.font(.caption)
  70. }
  71. ToolbarItemGroup(placement: .bottomBar) {
  72. Button {
  73. showingOverrideSheet = true
  74. } label: {
  75. Image(systemName: "clock.arrow.2.circlepath")
  76. .foregroundStyle(Color.primary, isOverrideActive ? Color.primary : Color.purple)
  77. }.tint(isOverrideActive ? Color.purple : nil)
  78. Button {
  79. showingTreatmentMenuSheet = true
  80. } label: {
  81. Image(systemName: "plus")
  82. .foregroundStyle(Color.bgDarkerDarkBlue)
  83. }
  84. .controlSize(.large)
  85. .buttonStyle(WatchOSButtonStyle())
  86. Button {
  87. showingTempTargetSheet = true
  88. } label: {
  89. Image(systemName: "target")
  90. .foregroundStyle(isTempTargetActive ? Color.primary : Color.green.opacity(0.75))
  91. }.tint(isTempTargetActive ? Color.green.opacity(0.75) : nil)
  92. }
  93. }
  94. .fullScreenCover(isPresented: $showingTreatmentMenuSheet) {
  95. TreatmentMenuView(selectedTreatment: $selectedTreatment) {
  96. handleTreatmentSelection()
  97. }
  98. .onAppear {
  99. continueToBolus = false
  100. }
  101. }
  102. .sheet(isPresented: $showingOverrideSheet) {
  103. OverridePresetsView(
  104. overridePresets: state.overridePresets,
  105. state: state
  106. )
  107. }
  108. .sheet(isPresented: $showingTempTargetSheet) {
  109. TempTargetPresetsView(
  110. tempTargetPresets: state.tempTargetPresets,
  111. state: state
  112. )
  113. }
  114. .navigationDestination(for: NavigationDestinations.self) { destination in
  115. switch destination {
  116. case .acknowledgmentPending:
  117. AcknowledgementPendingView(
  118. navigationPath: $navigationPath,
  119. state: state
  120. )
  121. case .carbInput:
  122. CarbsInputView(
  123. navigationPath: $navigationPath,
  124. state: state,
  125. continueToBolus: selectedTreatment == .mealBolusCombo
  126. )
  127. case .bolusInput:
  128. BolusInputView(
  129. navigationPath: $navigationPath,
  130. state: state
  131. )
  132. case .bolusConfirm:
  133. BolusConfirmationView(
  134. navigationPath: $navigationPath,
  135. state: state,
  136. bolusAmount: $state.bolusAmount,
  137. confirmationProgress: $state.confirmationProgress
  138. )
  139. }
  140. }
  141. }
  142. .blur(radius: state.showBolusProgressOverlay ? 3 : 0)
  143. .overlay {
  144. if state.showBolusProgressOverlay {
  145. BolusProgressOverlay(state: state)
  146. .transition(.opacity)
  147. }
  148. }
  149. }
  150. private func updateRotation(for trend: String?) {
  151. switch trend {
  152. case "DoubleUp",
  153. "SingleUp":
  154. rotationDegrees = -90
  155. case "FortyFiveUp":
  156. rotationDegrees = -45
  157. case "Flat":
  158. rotationDegrees = 0
  159. case "FortyFiveDown":
  160. rotationDegrees = 45
  161. case "DoubleDown",
  162. "SingleDown":
  163. rotationDegrees = 90
  164. default:
  165. rotationDegrees = 0
  166. }
  167. }
  168. private func handleTreatmentSelection() {
  169. showingTreatmentMenuSheet = false // Dismiss the sheet
  170. guard let treatment = selectedTreatment else { return }
  171. switch treatment {
  172. case .meal:
  173. navigationPath.append(NavigationDestinations.carbInput)
  174. case .bolus:
  175. navigationPath.append(NavigationDestinations.bolusInput)
  176. case .mealBolusCombo:
  177. navigationPath.append(NavigationDestinations.carbInput)
  178. }
  179. }
  180. }
  181. #Preview {
  182. TrioMainWatchView()
  183. }