| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- //
- // LiveActivityChartView.swift
- // FreeAPS
- //
- // Created by Cengiz Deniz on 17.10.24.
- //
- import Charts
- import Foundation
- import SwiftUI
- import WidgetKit
- struct LiveActivityChartView: View {
- @Environment(\.colorScheme) var colorScheme
- var context: ActivityViewContext<LiveActivityAttributes>
- var additionalState: LiveActivityAttributes.ContentAdditionalState
- var body: some View {
- let state = context.state
- let isMgdL: Bool = additionalState.unit == "mg/dL"
- // Determine scale
- let minValue = min(additionalState.chart.min() ?? 39, 39) as Decimal
- let maxValue = max(additionalState.chart.max() ?? 300, 300) as Decimal
- let yAxisRuleMarkMin = isMgdL ? state.lowGlucose : state.lowGlucose
- .asMmolL
- let yAxisRuleMarkMax = isMgdL ? state.highGlucose : state.highGlucose
- .asMmolL
- let target = isMgdL ? state.target : state.target.asMmolL
- let isOverrideActive = additionalState.isOverrideActive == true
- let calendar = Calendar.current
- let now = Date()
- let startDate = calendar.date(byAdding: .hour, value: -6, to: now) ?? now
- let endDate = isOverrideActive ? (calendar.date(byAdding: .hour, value: 2, to: now) ?? now) : now
- // 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
- let hardCodedLow = isMgdL ? Decimal(55) : 55.asMmolL
- let hardCodedHigh = isMgdL ? Decimal(220) : 220.asMmolL
- let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
- let highColor = Color.getDynamicGlucoseColor(
- glucoseValue: yAxisRuleMarkMax,
- highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : yAxisRuleMarkMax,
- lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : yAxisRuleMarkMin,
- targetGlucose: target,
- glucoseColorScheme: context.state.glucoseColorScheme
- )
- let lowColor = Color.getDynamicGlucoseColor(
- glucoseValue: yAxisRuleMarkMin,
- highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : yAxisRuleMarkMax,
- lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : yAxisRuleMarkMin,
- targetGlucose: target,
- glucoseColorScheme: context.state.glucoseColorScheme
- )
- Chart {
- RuleMark(y: .value("High", yAxisRuleMarkMax))
- .foregroundStyle(highColor)
- .lineStyle(.init(lineWidth: 1, dash: [5]))
- RuleMark(y: .value("Low", yAxisRuleMarkMin))
- .foregroundStyle(lowColor)
- .lineStyle(.init(lineWidth: 1, dash: [5]))
- RuleMark(y: .value("Target", target))
- .foregroundStyle(.green.gradient)
- .lineStyle(.init(lineWidth: 1.5))
- if isOverrideActive {
- drawActiveOverrides()
- }
- drawChart(yAxisRuleMarkMin: yAxisRuleMarkMin, yAxisRuleMarkMax: yAxisRuleMarkMax)
- }
- .chartYAxis {
- AxisMarks(position: .trailing) { _ in
- AxisGridLine(stroke: .init(lineWidth: 0.65, dash: [2, 3]))
- .foregroundStyle(Color.white.opacity(colorScheme == .light ? 1 : 0.5))
- AxisValueLabel().foregroundStyle(.primary).font(.footnote)
- }
- }
- .chartYScale(domain: additionalState.unit == "mg/dL" ? minValue ... maxValue : minValue.asMmolL ... maxValue.asMmolL)
- .chartYAxis(.hidden)
- .chartPlotStyle { plotContent in
- plotContent
- .background(
- RoundedRectangle(cornerRadius: 12)
- .fill(colorScheme == .light ? Color.black.opacity(0.275) : .clear)
- )
- .clipShape(RoundedRectangle(cornerRadius: 12))
- }
- .chartXScale(domain: startDate ... endDate)
- .chartXAxis {
- AxisMarks(position: .automatic) { _ in
- AxisGridLine(stroke: .init(lineWidth: 0.65, dash: [2, 3]))
- .foregroundStyle(Color.white.opacity(colorScheme == .light ? 1 : 0.5))
- }
- }
- }
- private func drawActiveOverrides() -> some ChartContent {
- let start: Date = context.state.detailedViewState?.overrideDate ?? .distantPast
- let duration = context.state.detailedViewState?.overrideDuration ?? 0
- let durationAsTimeInterval = TimeInterval((duration as NSDecimalNumber).doubleValue * 60) // return seconds
- let end: Date = start.addingTimeInterval(durationAsTimeInterval)
- let target = context.state.detailedViewState?.overrideTarget ?? 0
- return RuleMark(
- xStart: .value("Start", start, unit: .second),
- xEnd: .value("End", end, unit: .second),
- y: .value("Value", target)
- )
- .foregroundStyle(Color.purple.opacity(0.6))
- .lineStyle(.init(lineWidth: 8))
- }
- private func drawChart(yAxisRuleMarkMin _: Decimal, yAxisRuleMarkMax _: Decimal) -> some ChartContent {
- ForEach(additionalState.chart.indices, id: \.self) { index in
- let isMgdL = additionalState.unit == "mg/dL"
- let currentValue = additionalState.chart[index]
- let displayValue = isMgdL ? currentValue : currentValue.asMmolL
- let chartDate = additionalState.chartDate[index] ?? Date()
- // 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
- let hardCodedLow = Decimal(55)
- let hardCodedHigh = Decimal(220)
- let hasStaticColorScheme = context.state.glucoseColorScheme == "staticColor"
- let pointMarkColor = Color.getDynamicGlucoseColor(
- glucoseValue: currentValue,
- highGlucoseColorValue: !hasStaticColorScheme ? hardCodedHigh : context.state.highGlucose,
- lowGlucoseColorValue: !hasStaticColorScheme ? hardCodedLow : context.state.lowGlucose,
- targetGlucose: context.state.target,
- glucoseColorScheme: context.state.glucoseColorScheme
- )
- let pointMark = PointMark(
- x: .value("Time", chartDate),
- y: .value("Value", displayValue)
- ).symbolSize(16)
- pointMark.foregroundStyle(pointMarkColor)
- }
- }
- }
|