TrioMainWatchView.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import Charts
  2. import SwiftUI
  3. struct TrioMainWatchView: View {
  4. @State private var state = WatchState()
  5. @State private var isTreatmentMenuSheetPresented: Bool = false
  6. @State private var showingOverrideSheet = false
  7. @State private var currentPage: Double = 0
  8. @State private var rotationDegrees: Double = 0.0
  9. @State private var showingTempTargetSheet = false
  10. var body: some View {
  11. TabView(selection: $currentPage) {
  12. // Page 1: Current glucose and action buttons
  13. ScrollView {
  14. VStack(spacing: 10) {
  15. // IOB, COB, lastLoopTime Display
  16. VStack(alignment: .leading) {
  17. HStack {
  18. HStack {
  19. Image(systemName: "syringe.fill")
  20. .foregroundStyle(.blue)
  21. Text(state.iob ?? "--")
  22. }
  23. Spacer()
  24. HStack {
  25. Image(systemName: "fork.knife")
  26. .foregroundStyle(.orange)
  27. Text(state.cob ?? "--")
  28. }
  29. Spacer()
  30. // TODO: set loop colors conditionally, not hard coded
  31. HStack {
  32. Image(systemName: "circle")
  33. .foregroundStyle(.green)
  34. Text(state.lastLoopTime ?? "--")
  35. .padding(.trailing)
  36. }
  37. }
  38. }
  39. // Main Glucose Display
  40. ZStack {
  41. TrendShape(rotationDegrees: rotationDegrees)
  42. .animation(.spring(response: 0.5, dampingFraction: 0.6), value: rotationDegrees)
  43. VStack(alignment: .center) {
  44. Text(state.currentGlucose)
  45. .fontWeight(.semibold)
  46. .font(.system(.title, design: .rounded))
  47. if let delta = state.delta {
  48. Text(delta)
  49. .fontWeight(.semibold)
  50. .font(.system(.caption, design: .rounded))
  51. .foregroundStyle(.secondary)
  52. }
  53. }
  54. }.padding(.top)
  55. }
  56. .scenePadding()
  57. }
  58. .tag(0.0)
  59. .onChange(of: state.trend) { _, newTrend in
  60. withAnimation {
  61. updateRotation(for: newTrend)
  62. }
  63. }
  64. .toolbar {
  65. ToolbarItemGroup(placement: .bottomBar) {
  66. Button {
  67. showingOverrideSheet = true
  68. } label: {
  69. Image(systemName: "clock.arrow.2.circlepath")
  70. .foregroundStyle(Color.primary, Color.purple)
  71. }
  72. Button {
  73. isTreatmentMenuSheetPresented.toggle()
  74. } label: {
  75. Image(systemName: "plus")
  76. .foregroundStyle(Color.black)
  77. }
  78. .controlSize(.large)
  79. .buttonStyle(WatchOSButtonStyle(backgroundGradient: LinearGradient(colors: [
  80. Color(red: 0.7215686275, green: 0.3411764706, blue: 1), // #B857FF
  81. Color(red: 0.6235294118, green: 0.4235294118, blue: 0.9803921569), // #9F6CFA
  82. Color(red: 0.4862745098, green: 0.5450980392, blue: 0.9529411765), // #7C8BF3
  83. Color(red: 0.3411764706, green: 0.6666666667, blue: 0.9254901961), // #57AAEC
  84. Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902), // #43BBE9
  85. Color(
  86. red: 0.7215686275,
  87. green: 0.3411764706,
  88. blue: 1
  89. ) // #B857FF (repeated for seamless transition)
  90. ], startPoint: .topLeading, endPoint: .bottomTrailing)))
  91. Button {
  92. showingTempTargetSheet = true
  93. } label: {
  94. Image(systemName: "target")
  95. .foregroundStyle(.green.opacity(0.75))
  96. }
  97. }
  98. }
  99. // Page 2: Glucose chart
  100. GlucoseChartView(glucoseValues: state.glucoseValues)
  101. .tag(1.0)
  102. }
  103. .tabViewStyle(.verticalPage)
  104. .navigationBarHidden(true)
  105. .digitalCrownRotation($currentPage, from: 0, through: 1, by: 1)
  106. .blur(radius: isTreatmentMenuSheetPresented ? 10 : 0)
  107. .sheet(isPresented: $isTreatmentMenuSheetPresented) {
  108. TreatmentMenuView().presentationBackground(.clear)
  109. }
  110. .sheet(isPresented: $showingOverrideSheet) {
  111. OverridePresetsView(
  112. overridePresets: state.overridePresets,
  113. state: state
  114. )
  115. }
  116. .sheet(isPresented: $showingTempTargetSheet) {
  117. TempTargetPresetsView(
  118. tempTargetPresets: state.tempTargetPresets,
  119. state: state
  120. )
  121. }
  122. }
  123. struct WatchOSButtonStyle: ButtonStyle {
  124. var backgroundGradient: LinearGradient
  125. var foregroundColor: Color = .white
  126. var fontSize: Font = .title2
  127. func makeBody(configuration: Configuration) -> some View {
  128. configuration.label
  129. .font(fontSize)
  130. .fontWeight(.semibold)
  131. .foregroundColor(foregroundColor)
  132. .padding()
  133. .background(
  134. backgroundGradient // Custom background color
  135. .opacity(configuration.isPressed ? 0.8 : 1.0) // Simulates the press effect
  136. )
  137. .clipShape(Circle())
  138. .scaleEffect(configuration.isPressed ? 0.95 : 1.0) // Adds subtle scaling for press
  139. .animation(.easeInOut(duration: 0.1), value: configuration.isPressed) // Smooth animation
  140. }
  141. }
  142. private func updateRotation(for trend: String?) {
  143. switch trend {
  144. case "DoubleUp",
  145. "SingleUp":
  146. rotationDegrees = -90
  147. case "FortyFiveUp":
  148. rotationDegrees = -45
  149. case "Flat":
  150. rotationDegrees = 0
  151. case "FortyFiveDown":
  152. rotationDegrees = 45
  153. case "DoubleDown",
  154. "SingleDown":
  155. rotationDegrees = 90
  156. default:
  157. rotationDegrees = 0
  158. }
  159. }
  160. }
  161. #Preview {
  162. TrioMainWatchView()
  163. }