GlucoseTrendView.swift 4.7 KB

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