CurrentGlucoseView.swift 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import SwiftUI
  2. struct CurrentGlucoseView: View {
  3. @Binding var recentGlucose: BloodGlucose?
  4. @Binding var timerDate: Date
  5. @Binding var delta: Int?
  6. @Binding var units: GlucoseUnits
  7. @Binding var alarm: GlucoseAlarm?
  8. @Binding var lowGlucose: Decimal
  9. @Binding var highGlucose: Decimal
  10. @State private var rotationDegrees: Double = 0.0
  11. @State private var angularGradient = AngularGradient(colors: [
  12. // 184, 87, 255
  13. // 159, 108, 250
  14. // 124, 139, 243
  15. // 87, 170, 236
  16. // 67, 187, 233
  17. Color(red: 0.7215686275, green: 0.3411764706, blue: 1),
  18. Color(red: 0.6235294118, green: 0.4235294118, blue: 0.9803921569),
  19. Color(red: 0.4862745098, green: 0.5450980392, blue: 0.9529411765),
  20. Color(red: 0.3411764706, green: 0.6666666667, blue: 0.9254901961),
  21. Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902),
  22. Color(red: 0.7215686275, green: 0.3411764706, blue: 1)
  23. ], center: .center, startAngle: .degrees(270), endAngle: .degrees(-90))
  24. @Environment(\.colorScheme) var colorScheme
  25. private var glucoseFormatter: NumberFormatter {
  26. let formatter = NumberFormatter()
  27. formatter.numberStyle = .decimal
  28. formatter.maximumFractionDigits = 0
  29. if units == .mmolL {
  30. formatter.minimumFractionDigits = 1
  31. formatter.maximumFractionDigits = 1
  32. }
  33. formatter.roundingMode = .halfUp
  34. return formatter
  35. }
  36. private var deltaFormatter: NumberFormatter {
  37. let formatter = NumberFormatter()
  38. formatter.numberStyle = .decimal
  39. formatter.maximumFractionDigits = 1
  40. formatter.positivePrefix = " +"
  41. formatter.negativePrefix = " -"
  42. return formatter
  43. }
  44. private var timaAgoFormatter: NumberFormatter {
  45. let formatter = NumberFormatter()
  46. formatter.numberStyle = .decimal
  47. formatter.maximumFractionDigits = 0
  48. formatter.negativePrefix = ""
  49. return formatter
  50. }
  51. private var dateFormatter: DateFormatter {
  52. let formatter = DateFormatter()
  53. formatter.timeStyle = .short
  54. return formatter
  55. }
  56. var body: some View {
  57. let triangleColor = Color(red: 0.262745098, green: 0.7333333333, blue: 0.9137254902)
  58. ZStack {
  59. TrendShape(gradient: angularGradient, color: triangleColor)
  60. .rotationEffect(.degrees(rotationDegrees))
  61. VStack(alignment: .center) {
  62. HStack {
  63. Text(
  64. (recentGlucose?.glucose ?? 100) == 400 ? "HIGH" : recentGlucose?.glucose
  65. .map {
  66. glucoseFormatter
  67. .string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)! }
  68. ?? "--"
  69. )
  70. .font(.system(size: 40, weight: .bold))
  71. .foregroundColor(alarm == nil ? colourGlucoseText : .loopRed)
  72. }
  73. HStack {
  74. let minutesAgo = -1 * (recentGlucose?.dateString.timeIntervalSinceNow ?? 0) / 60
  75. let text = timaAgoFormatter.string(for: Double(minutesAgo)) ?? ""
  76. Text(
  77. minutesAgo <= 1 ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
  78. text + " " +
  79. NSLocalizedString("min", comment: "Short form for minutes") + " "
  80. )
  81. )
  82. .font(.caption2).foregroundColor(colorScheme == .dark ? Color.white.opacity(0.9) : Color.secondary)
  83. Text(
  84. delta
  85. .map {
  86. deltaFormatter.string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)!
  87. } ?? "--"
  88. )
  89. .font(.caption2).foregroundColor(colorScheme == .dark ? Color.white.opacity(0.9) : Color.secondary)
  90. }.frame(alignment: .top)
  91. }
  92. }
  93. .onChange(of: recentGlucose?.direction) { newDirection in
  94. withAnimation {
  95. switch newDirection {
  96. case .doubleUp,
  97. .singleUp,
  98. .tripleUp:
  99. rotationDegrees = -90
  100. case .fortyFiveUp:
  101. rotationDegrees = -45
  102. case .flat:
  103. rotationDegrees = 0
  104. case .fortyFiveDown:
  105. rotationDegrees = 45
  106. case .doubleDown,
  107. .singleDown,
  108. .tripleDown:
  109. rotationDegrees = 90
  110. case .none,
  111. .notComputable,
  112. .rateOutOfRange:
  113. rotationDegrees = 0
  114. @unknown default:
  115. rotationDegrees = 0
  116. }
  117. }
  118. }
  119. }
  120. var colourGlucoseText: Color {
  121. let whichGlucose = recentGlucose?.glucose ?? 0
  122. let defaultColor: Color = colorScheme == .dark ? .white : .black
  123. guard lowGlucose < highGlucose else { return .primary }
  124. switch whichGlucose {
  125. case 0 ..< Int(lowGlucose):
  126. return .loopRed
  127. case Int(lowGlucose) ..< Int(highGlucose):
  128. return defaultColor
  129. case Int(highGlucose)...:
  130. return .loopYellow
  131. default:
  132. return defaultColor
  133. }
  134. }
  135. }
  136. struct Triangle: Shape {
  137. func path(in rect: CGRect) -> Path {
  138. var path = Path()
  139. path.move(to: CGPoint(x: rect.midX, y: rect.minY + 15))
  140. path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
  141. path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY), control: CGPoint(x: rect.midX, y: rect.midY + 10))
  142. path.closeSubpath()
  143. return path
  144. }
  145. }
  146. struct TrendShape: View {
  147. @Environment(\.colorScheme) var colorScheme
  148. let gradient: AngularGradient
  149. let color: Color
  150. var body: some View {
  151. HStack(alignment: .center) {
  152. ZStack {
  153. Group {
  154. CircleShape(gradient: gradient)
  155. TriangleShape(color: color)
  156. }.shadow(color: Color.black.opacity(colorScheme == .dark ? 0.75 : 0.33), radius: colorScheme == .dark ? 5 : 3)
  157. CircleShape(gradient: gradient)
  158. }
  159. }
  160. }
  161. }
  162. struct CircleShape: View {
  163. @Environment(\.colorScheme) var colorScheme
  164. let gradient: AngularGradient
  165. var body: some View {
  166. let colorBackground: Color = colorScheme == .dark ? Color(
  167. red: 0.05490196078,
  168. green: 0.05490196078,
  169. blue: 0.05490196078
  170. ) : .white
  171. Circle()
  172. .stroke(gradient, lineWidth: 6)
  173. .background(Circle().fill(colorBackground))
  174. .frame(width: 130, height: 130)
  175. }
  176. }
  177. struct TriangleShape: View {
  178. let color: Color
  179. var body: some View {
  180. Triangle()
  181. .fill(color)
  182. .frame(width: 35, height: 35)
  183. .rotationEffect(.degrees(90))
  184. .offset(x: 85)
  185. }
  186. }