CurrentGlucoseView.swift 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. @Environment(\.colorScheme) var colorScheme
  12. private var glucoseFormatter: NumberFormatter {
  13. let formatter = NumberFormatter()
  14. formatter.numberStyle = .decimal
  15. formatter.maximumFractionDigits = 0
  16. if units == .mmolL {
  17. formatter.minimumFractionDigits = 1
  18. formatter.maximumFractionDigits = 1
  19. }
  20. formatter.roundingMode = .halfUp
  21. return formatter
  22. }
  23. private var deltaFormatter: NumberFormatter {
  24. let formatter = NumberFormatter()
  25. formatter.numberStyle = .decimal
  26. formatter.maximumFractionDigits = 1
  27. formatter.positivePrefix = " +"
  28. formatter.negativePrefix = " -"
  29. return formatter
  30. }
  31. private var timaAgoFormatter: NumberFormatter {
  32. let formatter = NumberFormatter()
  33. formatter.numberStyle = .decimal
  34. formatter.maximumFractionDigits = 0
  35. formatter.negativePrefix = ""
  36. return formatter
  37. }
  38. private var dateFormatter: DateFormatter {
  39. let formatter = DateFormatter()
  40. formatter.timeStyle = .short
  41. return formatter
  42. }
  43. var body: some View {
  44. let colourGlucoseText: Color = colorScheme == .dark ? .white : .black
  45. ZStack {
  46. TrendShape(color: colorOfGlucose)
  47. .rotationEffect(.degrees(rotationDegrees))
  48. VStack(alignment: .center) {
  49. HStack {
  50. Text(
  51. (recentGlucose?.glucose ?? 100) == 400 ? "HIGH" : recentGlucose?.glucose
  52. .map {
  53. glucoseFormatter
  54. .string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)! }
  55. ?? "--"
  56. )
  57. .font(.system(size: 40, weight: .bold))
  58. .foregroundColor(alarm == nil ? colourGlucoseText : .loopRed)
  59. }
  60. HStack {
  61. let minutesAgo = -1 * (recentGlucose?.dateString.timeIntervalSinceNow ?? 0) / 60
  62. let text = timaAgoFormatter.string(for: Double(minutesAgo)) ?? ""
  63. Text(
  64. minutesAgo <= 1 ? "< 1 " + NSLocalizedString("min", comment: "Short form for minutes") : (
  65. text + " " +
  66. NSLocalizedString("min", comment: "Short form for minutes") + " "
  67. )
  68. )
  69. .font(.caption2).foregroundColor(.secondary)
  70. Text(
  71. delta
  72. .map {
  73. deltaFormatter.string(from: Double(units == .mmolL ? $0.asMmolL : Decimal($0)) as NSNumber)!
  74. } ?? "--"
  75. )
  76. .font(.caption2).foregroundColor(.secondary)
  77. }.frame(alignment: .top)
  78. }
  79. }
  80. .onChange(of: recentGlucose?.direction) { newDirection in
  81. withAnimation {
  82. switch newDirection {
  83. case .doubleUp,
  84. .singleUp,
  85. .tripleUp:
  86. rotationDegrees = -90
  87. case .fortyFiveUp:
  88. rotationDegrees = -45
  89. case .flat:
  90. rotationDegrees = 0
  91. case .fortyFiveDown:
  92. rotationDegrees = 45
  93. case .doubleDown,
  94. .singleDown,
  95. .tripleDown:
  96. rotationDegrees = 90
  97. case .none,
  98. .notComputable,
  99. .rateOutOfRange:
  100. rotationDegrees = 0
  101. @unknown default:
  102. rotationDegrees = 0
  103. }
  104. }
  105. }
  106. }
  107. var colorOfGlucose: Color {
  108. let whichGlucose = recentGlucose?.glucose ?? 0
  109. guard lowGlucose < highGlucose else { return .primary }
  110. switch whichGlucose {
  111. case 0 ..< Int(lowGlucose):
  112. return .loopRed
  113. case Int(lowGlucose) ..< Int(highGlucose):
  114. return .loopGreen
  115. case Int(highGlucose)...:
  116. return .loopYellow
  117. default:
  118. return .loopYellow
  119. }
  120. }
  121. }
  122. struct Triangle: Shape {
  123. func path(in rect: CGRect) -> Path {
  124. var path = Path()
  125. let cornerRadius: CGFloat = 8
  126. path.move(to: CGPoint(x: rect.midX, y: rect.minY))
  127. path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius))
  128. path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - cornerRadius), control: CGPoint(x: rect.midX, y: rect.maxY))
  129. path.closeSubpath()
  130. return path
  131. }
  132. }
  133. struct TrendShape: View {
  134. let color: Color
  135. var body: some View {
  136. HStack(alignment: .center) {
  137. ZStack {
  138. CircleShape(color: color)
  139. TriangleShape(color: color)
  140. }
  141. }
  142. }
  143. }
  144. struct CircleShape: View {
  145. @Environment(\.colorScheme) var colorScheme
  146. let color: Color
  147. var body: some View {
  148. let colorBackground: Color = colorScheme == .dark ? .black : .white
  149. Circle()
  150. .stroke(color, lineWidth: 10)
  151. .shadow(radius: 3)
  152. .background(Circle().fill(colorBackground))
  153. .frame(width: 110, height: 110)
  154. }
  155. }
  156. struct TriangleShape: View {
  157. let color: Color
  158. var body: some View {
  159. Triangle()
  160. .fill(color)
  161. .frame(width: 30, height: 30)
  162. .rotationEffect(.degrees(90))
  163. .offset(x: 65)
  164. }
  165. }