CurrentGlucoseView.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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(.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(.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. angularGradient
  101. case .fortyFiveUp:
  102. rotationDegrees = -45
  103. angularGradient
  104. case .flat:
  105. rotationDegrees = 0
  106. angularGradient
  107. case .fortyFiveDown:
  108. rotationDegrees = 45
  109. angularGradient
  110. case .doubleDown,
  111. .singleDown,
  112. .tripleDown:
  113. rotationDegrees = 90
  114. angularGradient
  115. case .none,
  116. .notComputable,
  117. .rateOutOfRange:
  118. rotationDegrees = 0
  119. angularGradient
  120. @unknown default:
  121. rotationDegrees = 0
  122. angularGradient
  123. }
  124. }
  125. }
  126. }
  127. var colourGlucoseText: Color {
  128. let whichGlucose = recentGlucose?.glucose ?? 0
  129. let defaultColor: Color = colorScheme == .dark ? .white : .black
  130. guard lowGlucose < highGlucose else { return .primary }
  131. switch whichGlucose {
  132. case 0 ..< Int(lowGlucose):
  133. return .loopRed
  134. case Int(lowGlucose) ..< Int(highGlucose):
  135. return defaultColor
  136. case Int(highGlucose)...:
  137. return .loopYellow
  138. default:
  139. return defaultColor
  140. }
  141. }
  142. }
  143. struct Triangle: Shape {
  144. func path(in rect: CGRect) -> Path {
  145. var path = Path()
  146. let cornerRadius: CGFloat = 8
  147. path.move(to: CGPoint(x: rect.midX, y: rect.minY))
  148. path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
  149. path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - cornerRadius), control: CGPoint(x: rect.midX, y: rect.maxY))
  150. path.closeSubpath()
  151. return path
  152. }
  153. }
  154. struct TrendShape: View {
  155. let gradient: AngularGradient
  156. let color: Color
  157. var body: some View {
  158. HStack(alignment: .center) {
  159. ZStack {
  160. CircleShape(gradient: gradient)
  161. TriangleShape(color: color)
  162. }
  163. }
  164. }
  165. }
  166. struct CircleShape: View {
  167. @Environment(\.colorScheme) var colorScheme
  168. let gradient: AngularGradient
  169. var body: some View {
  170. let colorBackground: Color = colorScheme == .dark ? .black.opacity(0.8) : .white
  171. Circle()
  172. .stroke(gradient, lineWidth: 10)
  173. .shadow(
  174. color: colorScheme == .dark ? Color(red: 0.02745098039, green: 0.1098039216, blue: 0.1411764706) :
  175. Color.black.opacity(0.33),
  176. radius: 3
  177. )
  178. .background(Circle().fill(colorBackground))
  179. .frame(width: 110, height: 110)
  180. }
  181. }
  182. struct TriangleShape: View {
  183. let color: Color
  184. var body: some View {
  185. Triangle()
  186. .fill(color)
  187. .frame(width: 30, height: 30)
  188. .rotationEffect(.degrees(90))
  189. .offset(x: 65)
  190. }
  191. }