LiveActivityChartView.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. //
  2. // LiveActivityChartView.swift
  3. // Trio
  4. //
  5. // Created by Cengiz Deniz on 17.10.24.
  6. //
  7. import Charts
  8. import Foundation
  9. import SwiftUI
  10. import WidgetKit
  11. struct LiveActivityChartView: View {
  12. @Environment(\.colorScheme) var colorScheme
  13. var context: ActivityViewContext<LiveActivityAttributes>
  14. var additionalState: LiveActivityAttributes.ContentAdditionalState
  15. var body: some View {
  16. let state = context.state
  17. let isMgdL: Bool = state.unit == "mg/dL"
  18. // Determine scale
  19. let minValue = min(additionalState.chart.min() ?? 39, 39) as Decimal
  20. let maxValue = max(additionalState.chart.max() ?? 300, 300) as Decimal
  21. let yAxisRuleMarkMin = isMgdL ? state.lowGlucose : state.lowGlucose
  22. .asMmolL
  23. let yAxisRuleMarkMax = isMgdL ? state.highGlucose : state.highGlucose
  24. .asMmolL
  25. let target = isMgdL ? state.target : state.target.asMmolL
  26. let isOverrideActive = additionalState.isOverrideActive == true
  27. let calendar = Calendar.current
  28. let now = Date()
  29. let startDate = calendar.date(byAdding: .hour, value: -6, to: now) ?? now
  30. let endDate = isOverrideActive ? (calendar.date(byAdding: .hour, value: 2, to: now) ?? now) : now
  31. // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
  32. let hardCodedLow = isMgdL ? Decimal(55) : 55.asMmolL
  33. let hardCodedHigh = isMgdL ? Decimal(220) : 220.asMmolL
  34. let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
  35. let highColor = Color.getDynamicGlucoseColor(
  36. glucoseValue: yAxisRuleMarkMax,
  37. highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : yAxisRuleMarkMax,
  38. lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : yAxisRuleMarkMin,
  39. targetGlucose: target,
  40. glucoseColorScheme: context.state.glucoseColorScheme
  41. )
  42. let lowColor = Color.getDynamicGlucoseColor(
  43. glucoseValue: yAxisRuleMarkMin,
  44. highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : yAxisRuleMarkMax,
  45. lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : yAxisRuleMarkMin,
  46. targetGlucose: target,
  47. glucoseColorScheme: context.state.glucoseColorScheme
  48. )
  49. Chart {
  50. RuleMark(y: .value("High", yAxisRuleMarkMax))
  51. .foregroundStyle(highColor)
  52. .lineStyle(.init(lineWidth: 1, dash: [5]))
  53. RuleMark(y: .value("Low", yAxisRuleMarkMin))
  54. .foregroundStyle(lowColor)
  55. .lineStyle(.init(lineWidth: 1, dash: [5]))
  56. RuleMark(y: .value("Target", target))
  57. .foregroundStyle(.green.gradient)
  58. .lineStyle(.init(lineWidth: 1.5))
  59. if isOverrideActive {
  60. drawActiveOverrides()
  61. }
  62. drawChart(yAxisRuleMarkMin: yAxisRuleMarkMin, yAxisRuleMarkMax: yAxisRuleMarkMax)
  63. }
  64. .chartYAxis {
  65. AxisMarks(position: .trailing) { _ in
  66. AxisGridLine(stroke: .init(lineWidth: 0.65, dash: [2, 3]))
  67. .foregroundStyle(Color.white.opacity(colorScheme == .light ? 1 : 0.5))
  68. AxisValueLabel().foregroundStyle(.primary).font(.footnote)
  69. }
  70. }
  71. .chartYScale(domain: state.unit == "mg/dL" ? minValue ... maxValue : minValue.asMmolL ... maxValue.asMmolL)
  72. .chartYAxis(.hidden)
  73. .chartPlotStyle { plotContent in
  74. plotContent
  75. .background(
  76. RoundedRectangle(cornerRadius: 12)
  77. .fill(colorScheme == .light ? Color.black.opacity(0.2) : .clear)
  78. )
  79. .clipShape(RoundedRectangle(cornerRadius: 12))
  80. }
  81. .chartXScale(domain: startDate ... endDate)
  82. .chartXAxis {
  83. AxisMarks(position: .automatic) { _ in
  84. AxisGridLine(stroke: .init(lineWidth: 0.65, dash: [2, 3]))
  85. .foregroundStyle(Color.primary.opacity(colorScheme == .light ? 1 : 0.5))
  86. }
  87. }
  88. }
  89. private func drawActiveOverrides() -> some ChartContent {
  90. let start: Date = context.state.detailedViewState?.overrideDate ?? .distantPast
  91. let duration = context.state.detailedViewState?.overrideDuration ?? 0
  92. let durationAsTimeInterval = TimeInterval((duration as NSDecimalNumber).doubleValue * 60) // return seconds
  93. let end: Date = start.addingTimeInterval(durationAsTimeInterval)
  94. let target = context.state.detailedViewState?.overrideTarget ?? 0
  95. return RuleMark(
  96. xStart: .value("Start", start, unit: .second),
  97. xEnd: .value("End", end, unit: .second),
  98. y: .value("Value", target)
  99. )
  100. .foregroundStyle(Color.purple.opacity(0.6))
  101. .lineStyle(.init(lineWidth: 8))
  102. }
  103. private func drawChart(yAxisRuleMarkMin _: Decimal, yAxisRuleMarkMax _: Decimal) -> some ChartContent {
  104. ForEach(additionalState.chart.indices, id: \.self) { index in
  105. let isMgdL = context.state.unit == "mg/dL"
  106. let currentValue = additionalState.chart[index]
  107. let displayValue = isMgdL ? currentValue : currentValue.asMmolL
  108. let chartDate = additionalState.chartDate[index] ?? Date()
  109. // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
  110. let hardCodedLow = Decimal(55)
  111. let hardCodedHigh = Decimal(220)
  112. let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
  113. let pointMarkColor = Color.getDynamicGlucoseColor(
  114. glucoseValue: currentValue,
  115. highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : context.state.highGlucose,
  116. lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : context.state.lowGlucose,
  117. targetGlucose: context.state.target,
  118. glucoseColorScheme: context.state.glucoseColorScheme
  119. )
  120. let pointMark = PointMark(
  121. x: .value("Time", chartDate),
  122. y: .value("Value", displayValue)
  123. ).symbolSize(16)
  124. pointMark.foregroundStyle(pointMarkColor)
  125. }
  126. }
  127. }