CurrentGlucoseView.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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 var gradientColor = LinearGradient(
  12. gradient: Gradient(colors: [
  13. Color(red: 0.298, green: 0.071, blue: 0.4),
  14. Color(red: 0.435, green: 0.114, blue: 0.573),
  15. Color(red: 0.741, green: 0.204, blue: 0.914),
  16. Color(red: 0.831, green: 0.263, blue: 1)
  17. ]),
  18. startPoint: .leading,
  19. endPoint: .trailing
  20. )
  21. @Environment(\.colorScheme) var colorScheme
  22. private var glucoseFormatter: NumberFormatter {
  23. let formatter = NumberFormatter()
  24. formatter.numberStyle = .decimal
  25. formatter.maximumFractionDigits = 0
  26. if units == .mmolL {
  27. formatter.minimumFractionDigits = 1
  28. formatter.maximumFractionDigits = 1
  29. }
  30. formatter.roundingMode = .halfUp
  31. return formatter
  32. }
  33. private var deltaFormatter: NumberFormatter {
  34. let formatter = NumberFormatter()
  35. formatter.numberStyle = .decimal
  36. formatter.maximumFractionDigits = 1
  37. formatter.positivePrefix = " +"
  38. formatter.negativePrefix = " -"
  39. return formatter
  40. }
  41. private var timaAgoFormatter: NumberFormatter {
  42. let formatter = NumberFormatter()
  43. formatter.numberStyle = .decimal
  44. formatter.maximumFractionDigits = 0
  45. formatter.negativePrefix = ""
  46. return formatter
  47. }
  48. private var dateFormatter: DateFormatter {
  49. let formatter = DateFormatter()
  50. formatter.timeStyle = .short
  51. return formatter
  52. }
  53. var body: some View {
  54. let colourGlucoseText: Color = colorScheme == .dark ? .white : .black
  55. let triangleColor = Color(red: 0.831, green: 0.263, blue: 1)
  56. ZStack {
  57. TrendShape(gradient: gradientColor, color: triangleColor)
  58. .rotationEffect(.degrees(rotationDegrees))
  59. VStack(alignment: .center) {
  60. HStack {
  61. Text(
  62. (recentGlucose?.glucose ?? 100) == 400 ? "HIGH" : recentGlucose?.glucose
  63. .map {
  64. glucoseFormatter
  65. .string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)! }
  66. ?? "--"
  67. )
  68. .font(.system(size: 40, weight: .bold))
  69. .foregroundColor(alarm == nil ? colourGlucoseText : .loopRed)
  70. }
  71. HStack {
  72. let minutesAgo = -1 * (recentGlucose?.dateString.timeIntervalSinceNow ?? 0) / 60
  73. let text = timaAgoFormatter.string(for: Double(minutesAgo)) ?? ""
  74. Text(
  75. minutesAgo <= 1 ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
  76. text + " " +
  77. NSLocalizedString("min", comment: "Short form for minutes") + " "
  78. )
  79. )
  80. .font(.caption2).foregroundColor(.secondary)
  81. Text(
  82. delta
  83. .map {
  84. deltaFormatter.string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)!
  85. } ?? "--"
  86. )
  87. .font(.caption2).foregroundColor(.secondary)
  88. }.frame(alignment: .top)
  89. }
  90. }
  91. .onChange(of: recentGlucose?.direction) { newDirection in
  92. withAnimation {
  93. switch newDirection {
  94. case .doubleUp,
  95. .singleUp,
  96. .tripleUp:
  97. rotationDegrees = -90
  98. gradientColor = LinearGradient(
  99. gradient: Gradient(colors: [
  100. Color(red: 0.298, green: 0.071, blue: 0.4),
  101. Color(red: 0.435, green: 0.114, blue: 0.573),
  102. Color(red: 0.741, green: 0.204, blue: 0.914),
  103. Color(red: 0.831, green: 0.263, blue: 1)
  104. ]),
  105. startPoint: .bottom,
  106. endPoint: .top
  107. )
  108. case .fortyFiveUp:
  109. rotationDegrees = -45
  110. gradientColor = LinearGradient(
  111. gradient: Gradient(colors: [
  112. Color(red: 0.298, green: 0.071, blue: 0.4),
  113. Color(red: 0.435, green: 0.114, blue: 0.573),
  114. Color(red: 0.741, green: 0.204, blue: 0.914),
  115. Color(red: 0.831, green: 0.263, blue: 1)
  116. ]),
  117. startPoint: .bottomLeading,
  118. endPoint: .topTrailing
  119. )
  120. case .flat:
  121. rotationDegrees = 0
  122. gradientColor = LinearGradient(
  123. gradient: Gradient(colors: [
  124. Color(red: 0.298, green: 0.071, blue: 0.4),
  125. Color(red: 0.435, green: 0.114, blue: 0.573),
  126. Color(red: 0.741, green: 0.204, blue: 0.914),
  127. Color(red: 0.831, green: 0.263, blue: 1)
  128. ]),
  129. startPoint: .leading,
  130. endPoint: .trailing
  131. )
  132. case .fortyFiveDown:
  133. rotationDegrees = 45
  134. gradientColor = LinearGradient(
  135. gradient: Gradient(colors: [
  136. Color(red: 0.298, green: 0.071, blue: 0.4),
  137. Color(red: 0.435, green: 0.114, blue: 0.573),
  138. Color(red: 0.741, green: 0.204, blue: 0.914),
  139. Color(red: 0.831, green: 0.263, blue: 1)
  140. ]),
  141. startPoint: .topLeading,
  142. endPoint: .bottomTrailing
  143. )
  144. case .doubleDown,
  145. .singleDown,
  146. .tripleDown:
  147. rotationDegrees = 90
  148. gradientColor = LinearGradient(
  149. gradient: Gradient(colors: [
  150. Color(red: 0.298, green: 0.071, blue: 0.4),
  151. Color(red: 0.435, green: 0.114, blue: 0.573),
  152. Color(red: 0.741, green: 0.204, blue: 0.914),
  153. Color(red: 0.831, green: 0.263, blue: 1)
  154. ]),
  155. startPoint: .top,
  156. endPoint: .bottom
  157. )
  158. case .none,
  159. .notComputable,
  160. .rateOutOfRange:
  161. rotationDegrees = 0
  162. gradientColor = LinearGradient(
  163. gradient: Gradient(colors: [
  164. Color(red: 0.298, green: 0.071, blue: 0.4),
  165. Color(red: 0.435, green: 0.114, blue: 0.573),
  166. Color(red: 0.741, green: 0.204, blue: 0.914),
  167. Color(red: 0.831, green: 0.263, blue: 1)
  168. ]),
  169. startPoint: .leading,
  170. endPoint: .trailing
  171. )
  172. @unknown default:
  173. rotationDegrees = 0
  174. gradientColor = LinearGradient(
  175. gradient: Gradient(colors: [
  176. Color(red: 0.298, green: 0.071, blue: 0.4),
  177. Color(red: 0.435, green: 0.114, blue: 0.573),
  178. Color(red: 0.741, green: 0.204, blue: 0.914),
  179. Color(red: 0.831, green: 0.263, blue: 1)
  180. ]),
  181. startPoint: .leading,
  182. endPoint: .trailing
  183. )
  184. }
  185. }
  186. }
  187. }
  188. var colorOfGlucose: Color {
  189. let whichGlucose = recentGlucose?.glucose ?? 0
  190. guard lowGlucose < highGlucose else { return .primary }
  191. switch whichGlucose {
  192. case 0 ..< Int(lowGlucose):
  193. return .loopRed
  194. case Int(lowGlucose) ..< Int(highGlucose):
  195. return .loopGreen
  196. case Int(highGlucose)...:
  197. return .loopYellow
  198. default:
  199. return .loopYellow
  200. }
  201. }
  202. }
  203. struct Triangle: Shape {
  204. func path(in rect: CGRect) -> Path {
  205. var path = Path()
  206. let cornerRadius: CGFloat = 8
  207. path.move(to: CGPoint(x: rect.midX, y: rect.minY))
  208. path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
  209. path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - cornerRadius), control: CGPoint(x: rect.midX, y: rect.maxY))
  210. path.closeSubpath()
  211. return path
  212. }
  213. }
  214. struct TrendShape: View {
  215. let gradient: LinearGradient
  216. let color: Color
  217. var body: some View {
  218. HStack(alignment: .center) {
  219. ZStack {
  220. CircleShape(gradient: gradient)
  221. TriangleShape(color: color)
  222. }
  223. }
  224. }
  225. }
  226. struct CircleShape: View {
  227. @Environment(\.colorScheme) var colorScheme
  228. let gradient: LinearGradient
  229. var body: some View {
  230. let colorBackground: Color = colorScheme == .dark ? .black.opacity(0.8) : .white
  231. Circle()
  232. .stroke(gradient, lineWidth: 10)
  233. .shadow(radius: 3)
  234. .background(Circle().fill(colorBackground))
  235. .frame(width: 110, height: 110)
  236. }
  237. }
  238. struct TriangleShape: View {
  239. let color: Color
  240. var body: some View {
  241. Triangle()
  242. .fill(color)
  243. .frame(width: 30, height: 30)
  244. .rotationEffect(.degrees(90))
  245. .offset(x: 65)
  246. }
  247. }