GlucoseTrendView.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import SwiftUI
  2. struct GlucoseTrendView: View {
  3. let state: WatchState
  4. let rotationDegrees: Double
  5. /// Determines the status color based on the time elapsed since the last loop
  6. /// - Parameter timeString: The time string representing minutes since last loop (format: "X min")
  7. /// - Returns: A color indicating the status:
  8. /// - Green: <= 5 minutes
  9. /// - Yellow: 5-10 minutes
  10. /// - Red: > 10 minutes or invalid time
  11. private func statusColor(for timeString: String?) -> Color {
  12. guard let timeString = timeString,
  13. timeString != "--",
  14. let minutes = timeString.split(separator: " ").first.flatMap({ Int($0) })
  15. else {
  16. return .secondary
  17. }
  18. switch minutes {
  19. case ...5:
  20. return Color.loopGreen
  21. case 5 ... 10:
  22. return Color.loopYellow
  23. case 11...:
  24. return Color.loopRed
  25. default:
  26. return Color.secondary
  27. }
  28. }
  29. var circleSize: CGFloat {
  30. switch state.deviceType {
  31. case .watch40mm,
  32. .watch41mm,
  33. .watch42mm:
  34. return 86
  35. case .unknown,
  36. .watch44mm,
  37. .watch45mm:
  38. return 105
  39. case .watch49mm:
  40. return 105
  41. }
  42. }
  43. var lineWidth: CGFloat {
  44. switch state.deviceType {
  45. case .watch40mm,
  46. .watch41mm,
  47. .watch42mm:
  48. return 1
  49. case .unknown,
  50. .watch44mm,
  51. .watch45mm:
  52. return 1.5
  53. case .watch49mm:
  54. return 1.5
  55. }
  56. }
  57. var shadowRadius: CGFloat {
  58. switch state.deviceType {
  59. case .watch40mm,
  60. .watch41mm,
  61. .watch42mm:
  62. return 8
  63. case .unknown,
  64. .watch44mm,
  65. .watch45mm:
  66. return 12
  67. case .watch49mm:
  68. return 12
  69. }
  70. }
  71. var currentGlucoseFontSize: Font {
  72. switch state.deviceType {
  73. case .watch40mm,
  74. .watch41mm,
  75. .watch42mm:
  76. return .title2
  77. case .unknown,
  78. .watch44mm,
  79. .watch45mm:
  80. return .title
  81. case .watch49mm:
  82. return .title
  83. }
  84. }
  85. var minutesAgoFontSize: CGFloat {
  86. switch state.deviceType {
  87. case .watch40mm,
  88. .watch41mm,
  89. .watch42mm:
  90. return 9
  91. case .unknown,
  92. .watch44mm,
  93. .watch45mm:
  94. return 10
  95. case .watch49mm:
  96. return 10
  97. }
  98. }
  99. var body: some View {
  100. VStack {
  101. ZStack {
  102. Circle()
  103. .stroke(statusColor(for: state.lastLoopTime), lineWidth: lineWidth)
  104. .frame(width: circleSize, height: circleSize)
  105. .background(Circle().fill(Color.bgDarkBlue))
  106. .shadow(color: statusColor(for: state.lastLoopTime), radius: shadowRadius)
  107. TrendShape(rotationDegrees: rotationDegrees, deviceType: state.deviceType)
  108. .animation(.spring(response: 0.5, dampingFraction: 0.6), value: rotationDegrees)
  109. .shadow(color: Color.black.opacity(0.5), radius: 5)
  110. VStack(alignment: .center) {
  111. Text(state.currentGlucose)
  112. .fontWeight(.semibold)
  113. .font(currentGlucoseFontSize)
  114. .foregroundStyle(state.currentGlucoseColorString.toColor())
  115. if let delta = state.delta {
  116. Text(delta)
  117. .fontWeight(.semibold)
  118. .font(.system(.caption))
  119. .foregroundStyle(.secondary)
  120. }
  121. }
  122. }
  123. Text(state.lastLoopTime ?? "--").font(.system(size: minutesAgoFontSize))
  124. Spacer()
  125. }.frame(maxWidth: .infinity, maxHeight: .infinity)
  126. }
  127. }