TIRView.swift 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // LoopFollow
  2. // TIRView.swift
  3. import SwiftUI
  4. struct TIRView: View {
  5. @ObservedObject var viewModel: TIRViewModel
  6. var body: some View {
  7. Button(action: {
  8. viewModel.toggleTIRMode()
  9. }) {
  10. ZStack(alignment: .topTrailing) {
  11. VStack(alignment: .leading, spacing: 12) {
  12. HStack {
  13. Text(viewModel.showTITR ? "Time in Tight Range" : "Time in Range")
  14. .font(.caption)
  15. .foregroundColor(.secondary)
  16. Spacer()
  17. if let inRangeValue = viewModel.tirData.first(where: { $0.period == .average })?.inRange {
  18. Text(formatRange(inRangeValue))
  19. .font(.caption)
  20. .foregroundColor(.secondary)
  21. }
  22. }
  23. if !viewModel.tirData.isEmpty {
  24. TIRGraphView(tirData: viewModel.tirData)
  25. .frame(height: 250)
  26. .allowsHitTesting(false)
  27. .clipped()
  28. VStack(alignment: .leading, spacing: 8) {
  29. if let average = viewModel.tirData.first(where: { $0.period == .average }) {
  30. Text("Cutoffs in \(Storage.shared.units.value)")
  31. .foregroundColor(.secondary)
  32. TIRLegendItem(
  33. color: .orange,
  34. label: "Very High (\(veryHighCutoffText))",
  35. percentage: average.veryHigh
  36. )
  37. TIRLegendItem(
  38. color: .yellow,
  39. label: "High (\(highCutoffText))",
  40. percentage: average.high
  41. )
  42. TIRLegendItem(
  43. color: .green,
  44. label: "In Range (\(inRangeCutoffText))",
  45. percentage: average.inRange
  46. )
  47. TIRLegendItem(
  48. color: .red.opacity(0.5),
  49. label: "Low (\(lowCutoffText))",
  50. percentage: average.low
  51. )
  52. TIRLegendItem(
  53. color: .red.opacity(0.8),
  54. label: "Very Low (\(veryLowCutoffText))",
  55. percentage: average.veryLow
  56. )
  57. }
  58. }
  59. .font(.caption2)
  60. } else {
  61. Text("No data available")
  62. .font(.caption)
  63. .foregroundColor(.secondary)
  64. .frame(height: 250)
  65. }
  66. }
  67. .frame(maxWidth: .infinity, alignment: .leading)
  68. .padding()
  69. Image(systemName: "chevron.up.chevron.down")
  70. .font(.caption2)
  71. .foregroundColor(.secondary.opacity(0.5))
  72. .padding(8)
  73. }
  74. .background(Color(.systemGray6))
  75. .cornerRadius(12)
  76. }
  77. .buttonStyle(PlainButtonStyle())
  78. }
  79. private func formatRange(_: Double) -> String {
  80. let lowThreshold: Double
  81. let highThreshold: Double
  82. if Storage.shared.units.value == "mg/dL" {
  83. lowThreshold = 70.0
  84. highThreshold = viewModel.showTITR ? 140.0 : 180.0
  85. } else {
  86. lowThreshold = 3.9
  87. highThreshold = viewModel.showTITR ? 7.8 : 10.0
  88. }
  89. return String(format: "%.1f – %.1f %@", lowThreshold, highThreshold, Storage.shared.units.value)
  90. }
  91. private var veryLowCutoffText: String {
  92. "< \(formatThreshold(veryLowThreshold))"
  93. }
  94. private var lowCutoffText: String {
  95. "\(formatThreshold(veryLowThreshold))–<\(formatThreshold(lowThreshold))"
  96. }
  97. private var inRangeCutoffText: String {
  98. "\(formatThreshold(lowThreshold))–\(formatThreshold(highThreshold))"
  99. }
  100. private var highCutoffText: String {
  101. ">\(formatThreshold(highThreshold))–\(formatThreshold(veryHighThreshold))"
  102. }
  103. private var veryHighCutoffText: String {
  104. "> \(formatThreshold(veryHighThreshold))"
  105. }
  106. private var lowThreshold: Double {
  107. Storage.shared.units.value == "mg/dL" ? 70.0 : 3.9
  108. }
  109. private var highThreshold: Double {
  110. if Storage.shared.units.value == "mg/dL" {
  111. return viewModel.showTITR ? 140.0 : 180.0
  112. }
  113. return viewModel.showTITR ? 7.8 : 10.0
  114. }
  115. private var veryLowThreshold: Double {
  116. Storage.shared.units.value == "mg/dL" ? 54.0 : 3.0
  117. }
  118. private var veryHighThreshold: Double {
  119. Storage.shared.units.value == "mg/dL" ? 250.0 : 13.9
  120. }
  121. private func formatThreshold(_ value: Double) -> String {
  122. if Storage.shared.units.value == "mg/dL" {
  123. return String(format: "%.0f", value)
  124. }
  125. return String(format: "%.1f", value)
  126. }
  127. }
  128. struct TIRLegendItem: View {
  129. let color: Color
  130. let label: String
  131. let percentage: Double
  132. var body: some View {
  133. HStack(spacing: 8) {
  134. Rectangle()
  135. .fill(color)
  136. .frame(width: 16, height: 16)
  137. Text(String(format: "%.1f%%", percentage))
  138. .foregroundColor(.primary)
  139. Text(label)
  140. .foregroundColor(.secondary)
  141. Spacer()
  142. }
  143. }
  144. }