CurrentGlucoseView.swift 6.7 KB

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