updates to GlucoseChartType to configure new Siri intent type from the existing swiftui glucose chart
- extend and improve the current swiftui glucose chart - add siri glucose chart type - add conditional handlers for frame, aspectRatio and padding etc - tidy up ConstantsGlucoseChartSwiftUI file ro improve readability and remove unnecessary constants
This commit is contained in:
parent
2b327ac413
commit
6e0f8c8ad5
|
@ -55,6 +55,7 @@ extension XDripWidget.EntryView {
|
|||
.font(.caption)
|
||||
.foregroundStyle(.colorTertiary)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
}
|
||||
.widgetBackground(backgroundView: Color.black)
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ extension XDripWidget.EntryView {
|
|||
.font(.caption)
|
||||
.foregroundStyle(.colorTertiary)
|
||||
}
|
||||
.padding(.top, 6)
|
||||
}
|
||||
.widgetBackground(backgroundView: Color.black)
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ extension XDripWidget.EntryView {
|
|||
.font(.caption)
|
||||
.foregroundStyle(.colorTertiary)
|
||||
}
|
||||
.padding(.top, 6)
|
||||
}
|
||||
.widgetBackground(backgroundView: Color.black)
|
||||
}
|
||||
|
|
|
@ -178,7 +178,6 @@
|
|||
D4E499AB277B43E3000F8CBA /* TreatmentCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E499AA277B43E3000F8CBA /* TreatmentCollection.swift */; };
|
||||
D4E499AD277B4CE7000F8CBA /* DateOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4E499AC277B4CE7000F8CBA /* DateOnly.swift */; };
|
||||
D4FD899727772F9100689788 /* TreatmentEntryAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4FD899627772F9100689788 /* TreatmentEntryAccessor.swift */; };
|
||||
E4B8C71E2B3EBEE3006375E2 /* GlucoseIntentResponseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B8C71D2B3EBEE3006375E2 /* GlucoseIntentResponseView.swift */; };
|
||||
E4C006212B3DE8EC00D59303 /* GlucoseIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C006202B3DE8EC00D59303 /* GlucoseIntent.swift */; };
|
||||
F227BF192B9DF76D00CEEAAD /* SettingsViewContactImageSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F227BF182B9DF76D00CEEAAD /* SettingsViewContactImageSettingsViewModel.swift */; };
|
||||
F227BF1C2B9E426B00CEEAAD /* ContactImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F227BF1B2B9E426B00CEEAAD /* ContactImageManager.swift */; };
|
||||
|
@ -992,7 +991,6 @@
|
|||
D4E499AA277B43E3000F8CBA /* TreatmentCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentCollection.swift; sourceTree = "<group>"; };
|
||||
D4E499AC277B4CE7000F8CBA /* DateOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateOnly.swift; sourceTree = "<group>"; };
|
||||
D4FD899627772F9100689788 /* TreatmentEntryAccessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreatmentEntryAccessor.swift; sourceTree = "<group>"; };
|
||||
E4B8C71D2B3EBEE3006375E2 /* GlucoseIntentResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseIntentResponseView.swift; sourceTree = "<group>"; };
|
||||
E4C006202B3DE8EC00D59303 /* GlucoseIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseIntent.swift; sourceTree = "<group>"; };
|
||||
E4D530622B418FF80018C6A4 /* AppShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = "<group>"; };
|
||||
F227BF182B9DF76D00CEEAAD /* SettingsViewContactImageSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewContactImageSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -2051,7 +2049,6 @@
|
|||
children = (
|
||||
E4D530622B418FF80018C6A4 /* AppShortcuts.swift */,
|
||||
E4C006202B3DE8EC00D59303 /* GlucoseIntent.swift */,
|
||||
E4B8C71D2B3EBEE3006375E2 /* GlucoseIntentResponseView.swift */,
|
||||
);
|
||||
name = AppIntents;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4340,7 +4337,6 @@
|
|||
47B136DB2BC9BB55001E7ED3 /* ContactImageView.swift in Sources */,
|
||||
F830993023C928E0005741DF /* WatlaaBluetoothTransmitterDelegate.swift in Sources */,
|
||||
F8025C0A21D94FD700ECF0C0 /* CBManagerState.swift in Sources */,
|
||||
E4B8C71E2B3EBEE3006375E2 /* GlucoseIntentResponseView.swift in Sources */,
|
||||
47ADD2DF27FAF8630025E2F4 /* ChartPointsScatterDownTrianglesLayer.swift in Sources */,
|
||||
F8F9722723A5915900C3F17D /* CGMBluconTransmitter.swift in Sources */,
|
||||
F8E3A2A923D906C200E5E98A /* CalendarManager.swift in Sources */,
|
||||
|
|
|
@ -10,136 +10,115 @@ import SwiftUI
|
|||
|
||||
enum ConstantsGlucoseChartSwiftUI {
|
||||
|
||||
static let mmollToMgdl = 18.01801801801802
|
||||
static let mgDlToMmoll = 0.0555
|
||||
// ------------------------------------------
|
||||
// ----- SwiftUI Glucose Chart --------------
|
||||
// ------------------------------------------
|
||||
// default chart properties for all chart types
|
||||
static let yAxisLineSize: Double = 0.8
|
||||
static let yAxisLabelOffsetX: CGFloat = 0
|
||||
static let yAxisLabelOffsetY: CGFloat = 0
|
||||
|
||||
/// application name, appears in licenseInfo as title
|
||||
static let applicationName: String = {
|
||||
|
||||
guard let dictionary = Bundle.main.infoDictionary else {return "unknown"}
|
||||
|
||||
guard let version = dictionary["CFBundleDisplayName"] as? String else {return "unknown"}
|
||||
|
||||
return version
|
||||
|
||||
}()
|
||||
|
||||
static let yAxisLowHighLineColor = Color(white: 0.7)
|
||||
static let yAxisUrgentLowHighLineColor = Color(white: 0.6)
|
||||
|
||||
// live activity
|
||||
static let xAxisGridLineColor = Color(white: 0.5)
|
||||
static let xAxisLabelOffsetX: CGFloat = -12
|
||||
static let xAxisLabelOffsetY: CGFloat = -2
|
||||
static let xAxisIntervalBetweenValues: Int = 1
|
||||
static let xAxisLabelFirstClippingInMinutes: Double = 8 * 60
|
||||
static let xAxisLabelLastClippingInMinutes: Double = 12 * 60
|
||||
|
||||
static let cornerRadius: CGFloat = 2
|
||||
|
||||
|
||||
// ------------------------------------------
|
||||
// ----- Live Activities --------------------
|
||||
// ------------------------------------------
|
||||
// live activity (normal)
|
||||
static let viewWidthLiveActivityNormal: CGFloat = 180
|
||||
static let viewHeightLiveActivityNormal: CGFloat = 70
|
||||
static let hoursToShowLiveActivityNormal: Double = 3
|
||||
static let intervalBetweenXAxisValuesLiveActivityNormal: Int = 1
|
||||
static let glucoseCircleDiameterLiveActivityNormal: Double = 36
|
||||
|
||||
// live activity (large)
|
||||
static let viewWidthLiveActivityLarge: CGFloat = 340
|
||||
static let viewHeightLiveActivityLarge: CGFloat = 90
|
||||
static let hoursToShowLiveActivityLarge: Double = 8
|
||||
static let intervalBetweenXAxisValuesLiveActivityLarge: Int = 1
|
||||
static let glucoseCircleDiameterLiveActivityLarge: Double = 24
|
||||
|
||||
static let lowHighLineColorLiveActivity = Color(white: 0.6)
|
||||
static let urgentLowHighLineLiveActivity = Color(white: 0.4)
|
||||
static let xAxisGridLineColorLiveActivity = Color(white: 0.4)
|
||||
static let relativeYAxisLineSizeLiveActivity: Double = 1
|
||||
static let xAxisLabelOffsetLiveActivity: Double = -10
|
||||
|
||||
// dynamic island bottom (expanded)
|
||||
static let viewWidthDynamicIsland: CGFloat = 330
|
||||
static let viewHeightDynamicIsland: CGFloat = 70
|
||||
|
||||
static let lowHighLineColorDynamicIsland = Color(white: 0.6)
|
||||
static let urgentLowHighLineColorDynamicIsland = Color(white: 0.4)
|
||||
static let xAxisGridLineColorDynamicIsland = Color(white: 0.4)
|
||||
static let hoursToShowDynamicIsland: Double = 12
|
||||
static let intervalBetweenXAxisValuesDynamicIsland: Int = 2
|
||||
static let glucoseCircleDiameterDynamicIsland: Double = 14
|
||||
static let relativeYAxisLineSizeDynamicIsland: Double = 0.8
|
||||
static let xAxisLabelOffsetDynamicIsland: Double = -10
|
||||
|
||||
|
||||
// ------------------------------------------
|
||||
// ----- Watch app --------------------------
|
||||
// ------------------------------------------
|
||||
// watch chart
|
||||
static let viewWidthWatchApp: CGFloat = 190
|
||||
static let viewHeightWatchApp: CGFloat = 90
|
||||
static let hoursToShowWatchApp: Double = 4
|
||||
static let glucoseCircleDiameterWatchApp: Double = 20
|
||||
|
||||
// watch chart sizes for smaller watches
|
||||
static let viewWidthWatchAppSmall: CGFloat = 155
|
||||
static let viewHeightWatchAppSmall: CGFloat = 75
|
||||
|
||||
static let lowHighLineColorWatchApp = Color(white: 0.6)
|
||||
static let urgentLowHighLineColorWatchApp = Color(white: 0.4)
|
||||
static let xAxisGridLineColorWatchApp = Color(white: 0.3)
|
||||
static let hoursToShowWatchApp: Double = 4
|
||||
static let intervalBetweenXAxisValuesWatchApp: Int = 1
|
||||
static let glucoseCircleDiameterWatchApp: Double = 20
|
||||
static let relativeYAxisLineSizeWatchApp: Double = 0.8
|
||||
static let xAxisLabelOffsetWatchApp: Double = -10
|
||||
|
||||
// ------------------------------------------
|
||||
// ----- Watch Widgets/Complications --------
|
||||
// ------------------------------------------
|
||||
// watch complication .accessoryRectangular chart
|
||||
static let viewWidthWatchAccessoryRectangular: CGFloat = 180
|
||||
static let viewHeightWatchAccessoryRectangular: CGFloat = 55
|
||||
static let hoursToShowWatchAccessoryRectangular: Double = 5
|
||||
static let glucoseCircleDiameterWatchAccessoryRectangular: Double = 14
|
||||
|
||||
// watch complication sizes for smaller watches
|
||||
static let viewWidthWatchAccessoryRectangularSmall: CGFloat = 160
|
||||
static let viewHeightWatchAccessoryRectangularSmall: CGFloat = 45
|
||||
|
||||
static let lowHighLineColorWatchAccessoryRectangular = Color(white: 0.7)
|
||||
static let urgentLowHighLineColorWatchAccessoryRectangular = Color(white: 0.5)
|
||||
static let xAxisGridLineColorWatchAccessoryRectangular = Color(white: 0.4)
|
||||
static let hoursToShowWatchAccessoryRectangular: Double = 5
|
||||
static let intervalBetweenXAxisValuesWatchAccessoryRectangular: Int = 1
|
||||
static let glucoseCircleDiameterWatchAccessoryRectangular: Double = 14
|
||||
static let relativeYAxisLineSizeWatchAccessoryRectangular: Double = 0.8
|
||||
static let xAxisLabelOffsetWatchAccessoryRectangular: Double = -10
|
||||
|
||||
// ------------------------------------------
|
||||
// ----- iOS Widgets ------------------------
|
||||
// ------------------------------------------
|
||||
// widget systemSmall chart
|
||||
static let viewWidthWidgetSystemSmall: CGFloat = 120
|
||||
static let viewHeightWidgetSystemSmall: CGFloat = 80
|
||||
|
||||
static let lowHighLineColorWidgetSystemSmall = Color(white: 0.6)
|
||||
static let urgentLowHighLineColorWidgetSystemSmall = Color(white: 0.4)
|
||||
static let xAxisGridLineColorWidgetSystemSmall = Color(white: 0.3)
|
||||
static let hoursToShowWidgetSystemSmall: Double = 3
|
||||
static let intervalBetweenXAxisValuesWidgetSystemSmall: Int = 1
|
||||
static let glucoseCircleDiameterWidgetSystemSmall: Double = 20
|
||||
static let relativeYAxisLineSizeWidgetSystemSmall: Double = 0.8
|
||||
static let xAxisLabelOffsetWidgetSystemSmall: Double = -10
|
||||
|
||||
// widget systemMedium chart
|
||||
static let viewWidthWidgetSystemMedium: CGFloat = 300
|
||||
static let viewHeightWidgetSystemMedium: CGFloat = 80
|
||||
|
||||
static let lowHighLineColorWidgetSystemMedium = Color(white: 0.6)
|
||||
static let urgentLowHighLineColorWidgetSystemMedium = Color(white: 0.4)
|
||||
static let xAxisGridLineColorWidgetSystemMedium = Color(white: 0.3)
|
||||
static let hoursToShowWidgetSystemMedium: Double = 8
|
||||
static let intervalBetweenXAxisValuesWidgetSystemMedium: Int = 1
|
||||
static let glucoseCircleDiameterWidgetSystemMedium: Double = 14
|
||||
static let relativeYAxisLineSizeWidgetSystemMedium: Double = 0.8
|
||||
static let xAxisLabelOffsetWidgetSystemMedium: Double = -10
|
||||
|
||||
// widget systemLarge chart
|
||||
static let viewWidthWidgetSystemLarge: CGFloat = 300
|
||||
static let viewHeightWidgetSystemLarge: CGFloat = 260
|
||||
|
||||
static let lowHighLineColorWidgetSystemLarge = Color(white: 0.6)
|
||||
static let urgentLowHighLineColorWidgetSystemLarge = Color(white: 0.4)
|
||||
static let xAxisGridLineColorWidgetSystemLarge = Color(white: 0.3)
|
||||
static let viewHeightWidgetSystemLarge: CGFloat = 250
|
||||
static let hoursToShowWidgetSystemLarge: Double = 4
|
||||
static let intervalBetweenXAxisValuesWidgetSystemLarge: Int = 1
|
||||
static let glucoseCircleDiameterWidgetSystemLarge: Double = 30
|
||||
static let relativeYAxisLineSizeWidgetSystemLarge: Double = 0.8
|
||||
static let xAxisLabelOffsetWidgetSystemLarge: Double = -10
|
||||
|
||||
// widget (lock screen) .accessoryRectangular chart
|
||||
static let viewWidthWidgetAccessoryRectangular: CGFloat = 130
|
||||
static let viewHeightWidgetAccessoryRectangular: CGFloat = 40
|
||||
|
||||
static let lowHighLineColorWidgetAccessoryRectangular = Color(white: 0.7)
|
||||
static let urgentLowHighLineColorWidgetAccessoryRectangular = Color(white: 0.5)
|
||||
static let xAxisGridLineColorWidgetAccessoryRectangular = Color(white: 0.4)
|
||||
static let hoursToShowWidgetAccessoryRectangular: Double = 4
|
||||
static let intervalBetweenXAxisValuesWidgetAccessoryRectangular: Int = 1
|
||||
static let glucoseCircleDiameterWidgetAccessoryRectangular: Double = 14
|
||||
static let relativeYAxisLineSizeWidgetAccessoryRectangular: Double = 0.8
|
||||
static let xAxisLabelOffsetWidgetAccessoryRectangular: Double = -10
|
||||
|
||||
|
||||
// ------------------------------------------
|
||||
// ----- Siri Intent Chart ------------------
|
||||
// ------------------------------------------
|
||||
// siri glucose intent response chart
|
||||
static let viewWidthWidgetSiriGlucoseIntent: CGFloat = 320
|
||||
static let viewHeightWidgetSiriGlucoseIntent: CGFloat = 150
|
||||
static let hoursToShowWidgetSiriGlucoseIntent: Double = 4
|
||||
static let glucoseCircleDiameterSiriGlucoseIntent: Double = 20
|
||||
static let cornerRadiusSiriGlucoseIntent: Double = 0
|
||||
static let paddingSiriGlucoseIntent: Double = 10
|
||||
static let backgroundColorSiriGlucoseIntent: Color = .black // Color(red: 0.18, green: 0.18, blue: 0.18) originally from gshaviv
|
||||
}
|
||||
|
|
|
@ -20,4 +20,18 @@ extension View {
|
|||
return background(backgroundView)
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.avanderlee.com/swiftui/conditional-view-modifier/
|
||||
/// Applies the given transform if the given condition evaluates to `true`.
|
||||
/// - Parameters:
|
||||
/// - condition: The condition to evaluate.
|
||||
/// - transform: The transform to apply to the source `View`.
|
||||
/// - Returns: Either the original `View` or the modified `View` if the condition is `true`.
|
||||
@ViewBuilder func `if`<Content: View>(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
|
||||
if condition() {
|
||||
transform(self)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,28 +27,53 @@ struct GlucoseIntent: AppIntent {
|
|||
@MainActor
|
||||
func perform() async throws -> some ReturnsValue<Double> & ProvidesDialog & ShowsSnippetView {
|
||||
let coreDataManager = await CoreDataManager.create(for: ConstantsCoreData.modelName)
|
||||
|
||||
let bgReadingsAccessor = BgReadingsAccessor(coreDataManager: coreDataManager)
|
||||
|
||||
let bgReadings = bgReadingsAccessor.getLatestBgReadings(limit: nil, fromDate: Date(timeIntervalSinceNow: -14400), forSensor: nil, ignoreRawData: true, ignoreCalculatedValue: false).sorted { $0.timeStamp < $1.timeStamp }
|
||||
|
||||
guard let mostRecent = bgReadings.last else {
|
||||
throw IntentError.message("No glucose data")
|
||||
}
|
||||
|
||||
let value = UserDefaults.standard.bloodGlucoseUnitIsMgDl ? mostRecent.calculatedValue : (mostRecent.calculatedValue * ConstantsBloodGlucose.mgDlToMmoll)
|
||||
let valueString = UserDefaults.standard.bloodGlucoseUnitIsMgDl ? value.formatted(.number.precision(.fractionLength(0))) : value.formatted(.number.precision(.fractionLength(1)))
|
||||
|
||||
let value = mostRecent.calculatedValue.mgdlToMmol(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) // UserDefaults.standard.bloodGlucoseUnitIsMgDl ? mostRecent.calculatedValue : (mostRecent.calculatedValue * ConstantsBloodGlucose.mgDlToMmoll)
|
||||
|
||||
let valueString = value.bgValuetoString(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl) // UserDefaults.standard.bloodGlucoseUnitIsMgDl ? value.formatted(.number.precision(.fractionLength(0))) : value.formatted(.number.precision(.fractionLength(1)))
|
||||
|
||||
let trendDescription: LocalizedStringResource = switch mostRecent.slopeTrend() {
|
||||
case .droppingFast: "dropping fast"
|
||||
case .dropping: "dropping"
|
||||
case .moderatelyDropping: "moderately dropping"
|
||||
case .slowlyDropping: "slowly dropping"
|
||||
case .stable: "stable"
|
||||
case .moderatelyRising: "moderately rising"
|
||||
case .slowlyRising: "slowly rising"
|
||||
case .rising: "rising"
|
||||
case .risingFast: "rising fast"
|
||||
}
|
||||
|
||||
|
||||
var bgReadingValues: [Double] = []
|
||||
var bgReadingDates: [Date] = []
|
||||
|
||||
for bgReading in bgReadings {
|
||||
bgReadingValues.append(bgReading.calculatedValue)
|
||||
bgReadingDates.append(bgReading.timeStamp)
|
||||
}
|
||||
|
||||
var dialogString: IntentDialog = "Your blood glucose is currently \(valueString) and \(trendDescription)"
|
||||
|
||||
let minutesAgo = (bgReadingDates.last?.timeIntervalSinceNow ?? 999 ) / 60
|
||||
|
||||
let minutesAgoString = abs(Int(minutesAgo))
|
||||
|
||||
if minutesAgo < -30 {
|
||||
dialogString = "Sorry, there are no recent blood glucose values."
|
||||
} else if minutesAgo < -7 {
|
||||
dialogString = "\(minutesAgoString) minutes ago your blood glucose was \(valueString) "
|
||||
}
|
||||
|
||||
return .result(
|
||||
value: value,
|
||||
dialog: "Your blood glucose level is \(valueString) and is \(trendDescription)",
|
||||
view: GlucoseIntentResponseView(readings: bgReadings)
|
||||
dialog: dialogString,
|
||||
view: GlucoseChartView(glucoseChartType: .siriGlucoseIntent, bgReadingValues: bgReadingValues, bgReadingDates: bgReadingDates, isMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl, urgentLowLimitInMgDl: UserDefaults.standard.urgentLowMarkValue, lowLimitInMgDl: UserDefaults.standard.lowMarkValue, highLimitInMgDl: UserDefaults.standard.highMarkValue, urgentHighLimitInMgDl: UserDefaults.standard.urgentHighMarkValue, liveActivitySize: nil, hoursToShowScalingHours: nil, glucoseCircleDiameterScalingHours: nil, overrideChartHeight: nil, overrideChartWidth: nil)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -81,9 +106,9 @@ enum IntentError: Error, CustomLocalizedStringResourceConvertible {
|
|||
private enum Trend {
|
||||
case droppingFast
|
||||
case dropping
|
||||
case moderatelyDropping
|
||||
case slowlyDropping
|
||||
case stable
|
||||
case moderatelyRising
|
||||
case slowlyRising
|
||||
case rising
|
||||
case risingFast
|
||||
}
|
||||
|
@ -91,13 +116,20 @@ private enum Trend {
|
|||
private extension BgReading {
|
||||
func slopeTrend() -> Trend {
|
||||
switch calculatedValueSlope * 60000 {
|
||||
case ..<(-2): .droppingFast
|
||||
case -2 ..< -1: .dropping
|
||||
case -1 ..< -0.5: .moderatelyDropping
|
||||
case -0.5 ..< 0.5: .stable
|
||||
case 0.5 ..< 1: .moderatelyRising
|
||||
case 1 ..< 2: .rising
|
||||
default: .risingFast
|
||||
case ..<(-2):
|
||||
.droppingFast
|
||||
case -2 ..< -1:
|
||||
.dropping
|
||||
case -1 ..< -0.5:
|
||||
.slowlyDropping
|
||||
case -0.5 ..< 0.5:
|
||||
.stable
|
||||
case 0.5 ..< 1:
|
||||
.slowlyRising
|
||||
case 1 ..< 2:
|
||||
.rising
|
||||
default:
|
||||
.risingFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
//
|
||||
// GlucoseIntentResponseView.swift
|
||||
// xdrip
|
||||
//
|
||||
// Created by Guy Shaviv on 29/12/2023.
|
||||
// Copyright © 2023 Johan Degraeve. All rights reserved.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 16, *)
|
||||
struct GlucoseIntentResponseView: View {
|
||||
let readings: [BgReading]
|
||||
|
||||
func symbolColor(value: Double) -> Color {
|
||||
switch value {
|
||||
case ..<UserDefaults.standard.urgentLowMarkValue:
|
||||
.red
|
||||
case UserDefaults.standard.urgentLowMarkValue..<UserDefaults.standard.lowMarkValue:
|
||||
.yellow
|
||||
case UserDefaults.standard.highMarkValue..<UserDefaults.standard.urgentHighMarkValue:
|
||||
.yellow
|
||||
case UserDefaults.standard.urgentHighMarkValue...:
|
||||
.red
|
||||
default:
|
||||
.green
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let domain = min(readings.map(\.valueInUserUnits).min() ?? Double.greatestFiniteMagnitude, 70 * unitFactor) ... max(readings.map(\.valueInUserUnits).max() ?? -Double.greatestFiniteMagnitude, 180 * unitFactor)
|
||||
Chart {
|
||||
if domain.contains(UserDefaults.standard.urgentLowMarkValueInUserChosenUnit) {
|
||||
RuleMark(y: .value("", UserDefaults.standard.urgentLowMarkValueInUserChosenUnit))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [1, 3]))
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
if domain.contains(UserDefaults.standard.urgentLowMarkValueInUserChosenUnit) {
|
||||
RuleMark(y: .value("", UserDefaults.standard.urgentLowMarkValueInUserChosenUnit))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [1, 3]))
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
|
||||
if domain.contains(UserDefaults.standard.lowMarkValueInUserChosenUnit) {
|
||||
RuleMark(y: .value("", UserDefaults.standard.lowMarkValueInUserChosenUnit))
|
||||
.lineStyle(StrokeStyle(lineWidth: 2, dash: [2, 4]))
|
||||
.foregroundStyle(.yellow)
|
||||
}
|
||||
if domain.contains(UserDefaults.standard.highMarkValueInUserChosenUnit) {
|
||||
RuleMark(y: .value("", UserDefaults.standard.highMarkValueInUserChosenUnit))
|
||||
.lineStyle(StrokeStyle(lineWidth: 2, dash: [2, 4]))
|
||||
.foregroundStyle(.yellow)
|
||||
}
|
||||
|
||||
if domain.contains(UserDefaults.standard.targetMarkValueInUserChosenUnit) {
|
||||
RuleMark(y: .value("", UserDefaults.standard.targetMarkValueInUserChosenUnit))
|
||||
.lineStyle(StrokeStyle(lineWidth: 2, dash: [8, 8]))
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
|
||||
ForEach(readings, id: \.timeStamp) { reading in
|
||||
PointMark(x: .value("Time", reading.timeStamp),
|
||||
y: .value("BG", reading.valueInUserUnits))
|
||||
.symbol(Circle())
|
||||
.foregroundStyle(symbolColor(value: reading.calculatedValue))
|
||||
}
|
||||
}
|
||||
.chartXAxis {
|
||||
AxisMarks {
|
||||
if let v = $0.as(Date.self) {
|
||||
AxisValueLabel {
|
||||
Text(v.formatted(.dateTime.hour()))
|
||||
.foregroundStyle(Color.white)
|
||||
}
|
||||
AxisGridLine()
|
||||
.foregroundStyle(Color.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
.chartYAxis {
|
||||
AxisMarks(values: axisTicks(for: domain)) { value in
|
||||
if let v = value.as(Double.self) {
|
||||
AxisValueLabel {
|
||||
Text(v.formatted(.number.precision(.significantDigits(UserDefaults.standard.bloodGlucoseUnitIsMgDl ? 0 : 1))))
|
||||
.font(.caption2)
|
||||
}
|
||||
|
||||
if !userValues.contains(v) {
|
||||
AxisValueLabel()
|
||||
.foregroundStyle(Color.white)
|
||||
AxisGridLine()
|
||||
.foregroundStyle(Color.white)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.chartYScale(domain: domain)
|
||||
.aspectRatio(1.5, contentMode: .fit)
|
||||
.padding()
|
||||
.background {
|
||||
Color(red: 0.18, green: 0.18, blue: 0.19) // Match: this will match how Siri presents the response dialog in dark mode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BgReading {
|
||||
var valueInUserUnits: Double {
|
||||
calculatedValue * unitFactor
|
||||
}
|
||||
}
|
||||
|
||||
private var unitFactor: Double {
|
||||
UserDefaults.standard.bloodGlucoseUnitIsMgDl ? 1 : ConstantsBloodGlucose.mgDlToMmoll
|
||||
}
|
||||
|
||||
private var userValues = [UserDefaults.standard.urgentLowMarkValueInUserChosenUnit,
|
||||
UserDefaults.standard.lowMarkValueInUserChosenUnit,
|
||||
UserDefaults.standard.targetMarkValueInUserChosenUnit,
|
||||
UserDefaults.standard.highMarkValueInUserChosenUnit,
|
||||
UserDefaults.standard.urgentHighMarkValueInUserChosenUnit]
|
||||
|
||||
private func axisTicks(for domain: ClosedRange<Double>) -> [Double] {
|
||||
var values = [70, 180, 250].map { $0 * unitFactor }.filter { domain.contains($0) }
|
||||
for v in userValues where domain.contains(v) {
|
||||
if values.filter({ abs(v - $0) < 9.9 }).isEmpty {
|
||||
values.append(v)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
extension [Double] {
|
||||
var range: ClosedRange<Double> {
|
||||
(self.min() ?? -Double.greatestFiniteMagnitude) ... (self.max() ?? Double.greatestFiniteMagnitude)
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ public enum GlucoseChartType: Int, CaseIterable {
|
|||
case widgetSystemMedium = 5
|
||||
case widgetSystemLarge = 6
|
||||
case widgetAccessoryRectangular = 7
|
||||
case siriGlucoseIntent = 8
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
|
@ -44,9 +45,12 @@ public enum GlucoseChartType: Int, CaseIterable {
|
|||
return "Widget Chart .systemLarge"
|
||||
case .widgetAccessoryRectangular:
|
||||
return "Widget Chart .accessoryRectangular"
|
||||
case .siriGlucoseIntent:
|
||||
return "Siri Glucose Intent Chart"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - general chart properties
|
||||
|
||||
func viewSize(liveActivitySize: LiveActivitySize) -> (width: CGFloat, height: CGFloat) {
|
||||
switch self {
|
||||
|
@ -68,9 +72,11 @@ public enum GlucoseChartType: Int, CaseIterable {
|
|||
case .widgetSystemMedium:
|
||||
return (ConstantsGlucoseChartSwiftUI.viewWidthWidgetSystemMedium, ConstantsGlucoseChartSwiftUI.viewHeightWidgetSystemMedium)
|
||||
case .widgetSystemLarge:
|
||||
return (ConstantsGlucoseChartSwiftUI.viewWidthWidgetSystemMedium, ConstantsGlucoseChartSwiftUI.viewHeightWidgetSystemLarge)
|
||||
return (ConstantsGlucoseChartSwiftUI.viewWidthWidgetSystemLarge, ConstantsGlucoseChartSwiftUI.viewHeightWidgetSystemLarge)
|
||||
case .widgetAccessoryRectangular:
|
||||
return (ConstantsGlucoseChartSwiftUI.viewWidthWidgetAccessoryRectangular, ConstantsGlucoseChartSwiftUI.viewHeightWidgetAccessoryRectangular)
|
||||
case .siriGlucoseIntent:
|
||||
return (ConstantsGlucoseChartSwiftUI.viewWidthWidgetSiriGlucoseIntent, ConstantsGlucoseChartSwiftUI.viewHeightWidgetSiriGlucoseIntent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,33 +103,13 @@ public enum GlucoseChartType: Int, CaseIterable {
|
|||
return ConstantsGlucoseChartSwiftUI.hoursToShowWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.hoursToShowWidgetAccessoryRectangular
|
||||
case .siriGlucoseIntent:
|
||||
return ConstantsGlucoseChartSwiftUI.hoursToShowWidgetSiriGlucoseIntent
|
||||
}
|
||||
}
|
||||
|
||||
func intervalBetweenAxisValues(liveActivitySize: LiveActivitySize) -> Int {
|
||||
switch self {
|
||||
case .liveActivity:
|
||||
switch liveActivitySize {
|
||||
case .large:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesLiveActivityLarge
|
||||
default:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesLiveActivityNormal
|
||||
}
|
||||
case .dynamicIsland:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesDynamicIsland
|
||||
case .watchApp:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesWatchApp
|
||||
case .watchAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesWatchAccessoryRectangular
|
||||
case .widgetSystemSmall:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesWidgetSystemSmall
|
||||
case .widgetSystemMedium:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesWidgetSystemMedium
|
||||
case .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.intervalBetweenXAxisValuesWidgetAccessoryRectangular
|
||||
}
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisIntervalBetweenValues
|
||||
}
|
||||
|
||||
func glucoseCircleDiameter(liveActivitySize: LiveActivitySize) -> Double {
|
||||
|
@ -149,112 +135,120 @@ public enum GlucoseChartType: Int, CaseIterable {
|
|||
return ConstantsGlucoseChartSwiftUI.glucoseCircleDiameterWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.glucoseCircleDiameterWidgetAccessoryRectangular
|
||||
case .siriGlucoseIntent:
|
||||
return ConstantsGlucoseChartSwiftUI.glucoseCircleDiameterSiriGlucoseIntent
|
||||
}
|
||||
}
|
||||
|
||||
var lowHighLineColor: Color {
|
||||
func backgroundColor() -> Color {
|
||||
switch self {
|
||||
case .liveActivity:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorLiveActivity
|
||||
case .dynamicIsland:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorDynamicIsland
|
||||
case .watchApp:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorWatchApp
|
||||
case .watchAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorWatchAccessoryRectangular
|
||||
case .widgetSystemSmall:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorWidgetSystemSmall
|
||||
case .widgetSystemMedium:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorWidgetSystemMedium
|
||||
case .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.lowHighLineColorWidgetAccessoryRectangular
|
||||
case .siriGlucoseIntent:
|
||||
return ConstantsGlucoseChartSwiftUI.backgroundColorSiriGlucoseIntent
|
||||
default:
|
||||
return .black
|
||||
}
|
||||
}
|
||||
|
||||
var urgentLowHighLineColor: Color {
|
||||
func frame() -> Bool {
|
||||
switch self {
|
||||
case .liveActivity:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineLiveActivity
|
||||
case .dynamicIsland:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineColorDynamicIsland
|
||||
case .watchApp:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineColorWatchApp
|
||||
case .watchAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineColorWatchAccessoryRectangular
|
||||
case .widgetSystemSmall:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineColorWidgetSystemSmall
|
||||
case .widgetSystemMedium:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineColorWidgetSystemMedium
|
||||
case .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineColorWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.urgentLowHighLineColorWidgetAccessoryRectangular
|
||||
case .siriGlucoseIntent:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var relativeYAxisLineSize: Double {
|
||||
func aspectRatio() -> (enable: Bool, aspectRatio: CGFloat, contentMode: ContentMode) {
|
||||
switch self {
|
||||
case .liveActivity:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeLiveActivity
|
||||
case .dynamicIsland:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeDynamicIsland
|
||||
case .watchApp:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeWatchApp
|
||||
case .watchAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeWatchAccessoryRectangular
|
||||
case .widgetSystemSmall:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeWidgetSystemSmall
|
||||
case .widgetSystemMedium:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeWidgetSystemMedium
|
||||
case .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.relativeYAxisLineSizeWidgetAccessoryRectangular
|
||||
case .siriGlucoseIntent:
|
||||
return (true, 1.5, .fit)
|
||||
default:
|
||||
return (false, 1, .fill) // use anything here after false as it won't be used
|
||||
}
|
||||
}
|
||||
|
||||
var xAxisLabelOffset: Double {
|
||||
func cornerRadius() -> Double {
|
||||
switch self {
|
||||
case .liveActivity:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetLiveActivity
|
||||
case .dynamicIsland:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetDynamicIsland
|
||||
case .watchApp:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetWatchApp
|
||||
case .watchAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetWatchAccessoryRectangular
|
||||
case .widgetSystemSmall:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetWidgetSystemSmall
|
||||
case .widgetSystemMedium:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetWidgetSystemMedium
|
||||
case .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetWidgetAccessoryRectangular
|
||||
case .siriGlucoseIntent:
|
||||
return ConstantsGlucoseChartSwiftUI.cornerRadiusSiriGlucoseIntent
|
||||
default:
|
||||
return ConstantsGlucoseChartSwiftUI.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
var xAxisGridLineColor: Color {
|
||||
func padding() -> (enable: Bool, padding: CGFloat) {
|
||||
switch self {
|
||||
case .liveActivity:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorLiveActivity
|
||||
case .dynamicIsland:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorDynamicIsland
|
||||
case .watchApp:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorWatchApp
|
||||
case .watchAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorWatchAccessoryRectangular
|
||||
case .widgetSystemSmall:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorWidgetSystemSmall
|
||||
case .widgetSystemMedium:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorWidgetSystemMedium
|
||||
case .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorWidgetSystemLarge
|
||||
case .widgetAccessoryRectangular:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisGridLineColorWidgetAccessoryRectangular
|
||||
case .siriGlucoseIntent:
|
||||
return (true, ConstantsGlucoseChartSwiftUI.paddingSiriGlucoseIntent)
|
||||
default:
|
||||
return (false, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - x axis properties
|
||||
|
||||
func xAxisShowLabels() -> Bool {
|
||||
switch self {
|
||||
case .siriGlucoseIntent, .widgetSystemLarge:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func xAxisLabelEveryHours() -> Int {
|
||||
switch self {
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func xAxisLabelOffsetX() -> CGFloat {
|
||||
switch self {
|
||||
case .siriGlucoseIntent, .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetX
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func xAxisLabelOffsetY() -> CGFloat {
|
||||
switch self {
|
||||
case .siriGlucoseIntent, .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.xAxisLabelOffsetY
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - y axis properties
|
||||
|
||||
func yAxisShowLabels() -> Visibility {
|
||||
switch self {
|
||||
case .siriGlucoseIntent, .widgetSystemLarge:
|
||||
return .automatic
|
||||
default:
|
||||
return .hidden
|
||||
}
|
||||
}
|
||||
|
||||
func yAxisLabelOffsetX() -> CGFloat {
|
||||
switch self {
|
||||
case .siriGlucoseIntent, .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.yAxisLabelOffsetX
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func yAxisLabelOffsetY() -> CGFloat {
|
||||
switch self {
|
||||
case .siriGlucoseIntent, .widgetSystemLarge:
|
||||
return ConstantsGlucoseChartSwiftUI.yAxisLabelOffsetY
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ struct GlucoseChartView: View {
|
|||
var bgReadingValues: [Double]
|
||||
var bgReadingDates: [Date]
|
||||
|
||||
let glucoseChartType: GlucoseChartType
|
||||
let chartType: GlucoseChartType // shortened to chartType to make reading easier below
|
||||
let isMgDl: Bool
|
||||
let urgentLowLimitInMgDl: Double
|
||||
let lowLimitInMgDl: Double
|
||||
|
@ -30,7 +30,7 @@ struct GlucoseChartView: View {
|
|||
|
||||
init(glucoseChartType: GlucoseChartType, bgReadingValues: [Double]?, bgReadingDates: [Date]?, isMgDl: Bool, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double, liveActivitySize: LiveActivitySize?, hoursToShowScalingHours: Double?, glucoseCircleDiameterScalingHours: Double?, overrideChartHeight: Double?, overrideChartWidth: Double?) {
|
||||
|
||||
self.glucoseChartType = glucoseChartType
|
||||
self.chartType = glucoseChartType
|
||||
self.isMgDl = isMgDl
|
||||
self.urgentLowLimitInMgDl = urgentLowLimitInMgDl
|
||||
self.lowLimitInMgDl = lowLimitInMgDl
|
||||
|
@ -40,15 +40,16 @@ struct GlucoseChartView: View {
|
|||
|
||||
// here we want to automatically set the hoursToShow based upon the chart type, but some chart instances might need
|
||||
// this to be overriden such as for zooming in/out of the chart (i.e. the Watch App)
|
||||
self.hoursToShow = hoursToShowScalingHours ?? glucoseChartType.hoursToShow(liveActivitySize: self.liveActivitySize)
|
||||
self.hoursToShow = hoursToShowScalingHours ?? chartType.hoursToShow(liveActivitySize: self.liveActivitySize)
|
||||
|
||||
self.chartHeight = overrideChartHeight ?? glucoseChartType.viewSize(liveActivitySize: self.liveActivitySize).height
|
||||
self.chartWidth = overrideChartWidth ?? glucoseChartType.viewSize(liveActivitySize: self.liveActivitySize).width
|
||||
self.chartHeight = overrideChartHeight ?? chartType.viewSize(liveActivitySize: self.liveActivitySize).height
|
||||
|
||||
self.chartWidth = overrideChartWidth ?? chartType.viewSize(liveActivitySize: self.liveActivitySize).width
|
||||
|
||||
// apply a scale to the glucoseCircleDiameter if an override value is passed
|
||||
self.glucoseCircleDiameter = glucoseChartType.glucoseCircleDiameter(liveActivitySize: self.liveActivitySize) * ((glucoseCircleDiameterScalingHours ?? self.hoursToShow) / self.hoursToShow)
|
||||
self.glucoseCircleDiameter = chartType.glucoseCircleDiameter(liveActivitySize: self.liveActivitySize) * ((glucoseCircleDiameterScalingHours ?? self.hoursToShow) / self.hoursToShow)
|
||||
|
||||
// as all widget instances are passed 12 hours of bg values, we must initialize this instance to use only the amount of hours of value required by the glucoseChartType passed
|
||||
// as all widget instances are passed 12 hours of bg values, we must initialize this instance to use only the amount of hours of value required by the chartType passed
|
||||
self.bgReadingValues = []
|
||||
self.bgReadingDates = []
|
||||
|
||||
|
@ -88,7 +89,7 @@ struct GlucoseChartView: View {
|
|||
let mappingArray = Array(1...amountOfFullHours)
|
||||
|
||||
/// set the stride count interval to make sure we don't add too many labels to the x-axis if the user wants to view >6 hours
|
||||
let intervalBetweenAxisValues: Int = glucoseChartType.intervalBetweenAxisValues(liveActivitySize: liveActivitySize)
|
||||
let intervalBetweenAxisValues: Int = chartType.intervalBetweenAxisValues(liveActivitySize: liveActivitySize)
|
||||
|
||||
/// first, for each int in mappingArray, we create a Date, starting with the lower hour + 1 hour - we will create 5 in this example, starting with hour 08 (7 + 3600 seconds)
|
||||
let startDateLower = Date(timeIntervalSinceReferenceDate:
|
||||
|
@ -104,32 +105,33 @@ struct GlucoseChartView: View {
|
|||
|
||||
|
||||
var body: some View {
|
||||
|
||||
let domain = (min((bgReadingValues.min() ?? 40), urgentLowLimitInMgDl) - 6) ... (max((bgReadingValues.max() ?? urgentHighLimitInMgDl), urgentHighLimitInMgDl) + 6)
|
||||
|
||||
let yAxisLineSize = ConstantsGlucoseChartSwiftUI.yAxisLineSize
|
||||
|
||||
Chart {
|
||||
if domain.contains(urgentLowLimitInMgDl) {
|
||||
RuleMark(y: .value("", urgentLowLimitInMgDl))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartType.relativeYAxisLineSize, dash: [2 * glucoseChartType.relativeYAxisLineSize, 6 * glucoseChartType.relativeYAxisLineSize]))
|
||||
.foregroundStyle(glucoseChartType.urgentLowHighLineColor)
|
||||
.lineStyle(StrokeStyle(lineWidth: yAxisLineSize, dash: [2 * yAxisLineSize, 6 * yAxisLineSize]))
|
||||
.foregroundStyle(ConstantsGlucoseChartSwiftUI.yAxisUrgentLowHighLineColor)
|
||||
}
|
||||
|
||||
if domain.contains(urgentHighLimitInMgDl) {
|
||||
RuleMark(y: .value("", urgentHighLimitInMgDl))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartType.relativeYAxisLineSize, dash: [2 * glucoseChartType.relativeYAxisLineSize, 6 * glucoseChartType.relativeYAxisLineSize]))
|
||||
.foregroundStyle(glucoseChartType.urgentLowHighLineColor)
|
||||
.lineStyle(StrokeStyle(lineWidth: yAxisLineSize, dash: [2 * yAxisLineSize, 6 * yAxisLineSize]))
|
||||
.foregroundStyle(ConstantsGlucoseChartSwiftUI.yAxisUrgentLowHighLineColor)
|
||||
}
|
||||
|
||||
if domain.contains(lowLimitInMgDl) {
|
||||
RuleMark(y: .value("", lowLimitInMgDl))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartType.relativeYAxisLineSize, dash: [4 * glucoseChartType.relativeYAxisLineSize, 3 * glucoseChartType.relativeYAxisLineSize]))
|
||||
.foregroundStyle(glucoseChartType.lowHighLineColor)
|
||||
.lineStyle(StrokeStyle(lineWidth: yAxisLineSize, dash: [4 * yAxisLineSize, 3 * yAxisLineSize]))
|
||||
.foregroundStyle(ConstantsGlucoseChartSwiftUI.yAxisLowHighLineColor)
|
||||
}
|
||||
|
||||
if domain.contains(highLimitInMgDl) {
|
||||
RuleMark(y: .value("", highLimitInMgDl))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1 * glucoseChartType.relativeYAxisLineSize, dash: [4 * glucoseChartType.relativeYAxisLineSize, 3 * glucoseChartType.relativeYAxisLineSize]))
|
||||
.foregroundStyle(glucoseChartType.lowHighLineColor)
|
||||
.lineStyle(StrokeStyle(lineWidth: yAxisLineSize, dash: [4 * yAxisLineSize, 3 * yAxisLineSize]))
|
||||
.foregroundStyle(ConstantsGlucoseChartSwiftUI.yAxisLowHighLineColor)
|
||||
}
|
||||
|
||||
// add a phantom glucose point at the beginning of the timeline to fix the start point in case there are no glucose values at that time (for instances after starting a new sensor)
|
||||
|
@ -157,15 +159,59 @@ struct GlucoseChartView: View {
|
|||
.foregroundStyle(.clear)
|
||||
}
|
||||
.chartXAxis {
|
||||
AxisMarks(values: .automatic(desiredCount: Int(hoursToShow))) {
|
||||
if $0.as(Date.self) != nil {
|
||||
AxisMarks(values: .stride(by: .hour, count: chartType.xAxisLabelEveryHours())) {
|
||||
if let value = $0.as(Date.self) {
|
||||
if chartType.xAxisShowLabels() {
|
||||
AxisValueLabel {
|
||||
let shouldHideLabel = abs(Date().distance(to: value)) < ConstantsGlucoseChartSwiftUI.xAxisLabelFirstClippingInMinutes || abs(Date().addingTimeInterval(-hoursToShow * 3600).distance(to: value)) < ConstantsGlucoseChartSwiftUI.xAxisLabelLastClippingInMinutes ? true : false
|
||||
|
||||
Text(!shouldHideLabel ? value.formatted(.dateTime.hour()) : "")
|
||||
.foregroundStyle(Color(.colorSecondary))
|
||||
.font(.footnote)
|
||||
.offset(x: chartType.xAxisLabelOffsetX(), y: chartType.xAxisLabelOffsetY())
|
||||
}
|
||||
}
|
||||
|
||||
AxisGridLine()
|
||||
.foregroundStyle(glucoseChartType.xAxisGridLineColor)
|
||||
.foregroundStyle(ConstantsGlucoseChartSwiftUI.xAxisGridLineColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
.chartYAxis(.hidden)
|
||||
.chartYAxis {
|
||||
AxisMarks(values: [lowLimitInMgDl, highLimitInMgDl]) {
|
||||
if let value = $0.as(Double.self) {
|
||||
AxisValueLabel {
|
||||
Text(value.mgdlToMmolAndToString(mgdl: isMgDl))
|
||||
.foregroundStyle(Color(.colorPrimary))
|
||||
.font(.footnote)
|
||||
.offset(x: chartType.yAxisLabelOffsetX(), y: chartType.yAxisLabelOffsetY())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AxisMarks(values: [urgentLowLimitInMgDl, urgentHighLimitInMgDl]) {
|
||||
if let value = $0.as(Double.self) {
|
||||
AxisValueLabel {
|
||||
Text(value.mgdlToMmolAndToString(mgdl: isMgDl))
|
||||
.foregroundStyle(Color(.colorSecondary))
|
||||
.font(.footnote)
|
||||
.offset(x: chartType.yAxisLabelOffsetX(), y: chartType.yAxisLabelOffsetY())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.if({ return chartType.frame() ? true : false }()) { view in
|
||||
view.frame(width: chartWidth, height: chartHeight)
|
||||
}
|
||||
.if({ return chartType.aspectRatio().enable ? true : false }()) { view in
|
||||
view.aspectRatio(chartType.aspectRatio().aspectRatio, contentMode: chartType.aspectRatio().contentMode)
|
||||
}
|
||||
.if({ return chartType.padding().enable ? true : false }()) { view in
|
||||
view.padding(chartType.padding().padding)
|
||||
}
|
||||
.chartYAxis(chartType.yAxisShowLabels())
|
||||
.chartYScale(domain: domain)
|
||||
.frame(width: chartWidth, height: chartHeight)
|
||||
.background(chartType.backgroundColor())
|
||||
.clipShape(RoundedRectangle(cornerRadius: chartType.cornerRadius()))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue