|
|
@@ -14,6 +14,12 @@ struct DotInfo {
|
|
|
let value: Decimal
|
|
|
}
|
|
|
|
|
|
+struct AnnouncementDot {
|
|
|
+ let rect: CGRect
|
|
|
+ let value: Decimal
|
|
|
+ let note: String
|
|
|
+}
|
|
|
+
|
|
|
typealias GlucoseYRange = (minValue: Int, minY: CGFloat, maxValue: Int, maxY: CGFloat)
|
|
|
|
|
|
struct MainChartView: View {
|
|
|
@@ -33,6 +39,19 @@ struct MainChartView: View {
|
|
|
static let fpuSize: CGFloat = 5
|
|
|
static let carbsScale: CGFloat = 0.3
|
|
|
static let fpuScale: CGFloat = 1
|
|
|
+ static let announcementSize: CGFloat = 8
|
|
|
+ static let announcementScale: CGFloat = 2.5
|
|
|
+ static let owlSeize: CGFloat = 25
|
|
|
+ static let owlOffset: CGFloat = 60
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum Command {
|
|
|
+ static let open = "🟢"
|
|
|
+ static let closed = "🔴"
|
|
|
+ static let suspend = "❌"
|
|
|
+ static let resume = "✅"
|
|
|
+ static let tempbasal = "💧..."
|
|
|
+ static let bolus = "💧"
|
|
|
}
|
|
|
|
|
|
@Binding var glucose: [BloodGlucose]
|
|
|
@@ -41,6 +60,7 @@ struct MainChartView: View {
|
|
|
@Binding var tempBasals: [PumpHistoryEvent]
|
|
|
@Binding var boluses: [PumpHistoryEvent]
|
|
|
@Binding var suspensions: [PumpHistoryEvent]
|
|
|
+ @Binding var announcement: [Announcement]
|
|
|
@Binding var hours: Int
|
|
|
@Binding var maxBasal: Decimal
|
|
|
@Binding var autotunedBasalProfile: [BasalProfileEntry]
|
|
|
@@ -60,6 +80,8 @@ struct MainChartView: View {
|
|
|
@State var didAppearTrigger = false
|
|
|
@State private var glucoseDots: [CGRect] = []
|
|
|
@State private var manualGlucoseDots: [CGRect] = []
|
|
|
+ @State private var announcementDots: [AnnouncementDot] = []
|
|
|
+ @State private var announcementPath = Path()
|
|
|
@State private var manualGlucoseDotsCenter: [CGRect] = []
|
|
|
@State private var unSmoothedGlucoseDots: [CGRect] = []
|
|
|
@State private var predictionDots: [PredictionType: [CGRect]] = [:]
|
|
|
@@ -277,6 +299,7 @@ struct MainChartView: View {
|
|
|
glucoseView(fullSize: fullSize)
|
|
|
manualGlucoseView(fullSize: fullSize)
|
|
|
manualGlucoseCenterView(fullSize: fullSize)
|
|
|
+ announcementView(fullSize: fullSize)
|
|
|
predictionsView(fullSize: fullSize)
|
|
|
}
|
|
|
timeLabelsView(fullSize: fullSize)
|
|
|
@@ -367,6 +390,35 @@ struct MainChartView: View {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private func announcementView(fullSize: CGSize) -> some View {
|
|
|
+ ZStack {
|
|
|
+ ForEach(announcementDots, id: \.rect.minX) { info -> AnyView in
|
|
|
+ let position = CGPoint(x: info.rect.midX + 5, y: info.rect.maxY - Config.owlOffset)
|
|
|
+ let type: String =
|
|
|
+ info.note.contains("true") ?
|
|
|
+ Command.open :
|
|
|
+ info.note.contains("false") ?
|
|
|
+ Command.closed :
|
|
|
+ info.note.contains("suspend") ?
|
|
|
+ Command.suspend :
|
|
|
+ info.note.contains("resume") ?
|
|
|
+ Command.resume :
|
|
|
+ info.note.contains("tempbasal") ?
|
|
|
+ Command.tempbasal : Command.bolus
|
|
|
+ VStack {
|
|
|
+ Text(type).font(.caption2).foregroundStyle(Color(.tempBasal))
|
|
|
+ Image("owl").resizable().frame(maxWidth: Config.owlSeize, maxHeight: Config.owlSeize).scaledToFill()
|
|
|
+ }.position(position).asAny()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .onChange(of: announcement) { _ in
|
|
|
+ calculateAnnouncementDots(fullSize: fullSize)
|
|
|
+ }
|
|
|
+ .onChange(of: didAppearTrigger) { _ in
|
|
|
+ calculateAnnouncementDots(fullSize: fullSize)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private func manualGlucoseCenterView(fullSize: CGSize) -> some View {
|
|
|
Path { path in
|
|
|
for rect in manualGlucoseDotsCenter {
|
|
|
@@ -530,6 +582,7 @@ extension MainChartView {
|
|
|
calculateGlucoseDots(fullSize: fullSize)
|
|
|
calculateManualGlucoseDots(fullSize: fullSize)
|
|
|
calculateManualGlucoseDotsCenter(fullSize: fullSize)
|
|
|
+ calculateAnnouncementDots(fullSize: fullSize)
|
|
|
calculateUnSmoothedGlucoseDots(fullSize: fullSize)
|
|
|
calculateBolusDots(fullSize: fullSize)
|
|
|
calculateCarbsDots(fullSize: fullSize)
|
|
|
@@ -587,6 +640,30 @@ extension MainChartView {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private func calculateAnnouncementDots(fullSize: CGSize) {
|
|
|
+ calculationQueue.async {
|
|
|
+ let dots = announcement.map { value -> AnnouncementDot in
|
|
|
+ let center = timeToInterpolatedPoint(value.createdAt.timeIntervalSince1970, fullSize: fullSize)
|
|
|
+ let size = Config.announcementSize * Config.announcementScale
|
|
|
+ let rect = CGRect(x: center.x - size / 2, y: center.y - size / 2, width: size, height: size)
|
|
|
+ let note = value.notes
|
|
|
+ return AnnouncementDot(rect: rect, value: 10, note: note)
|
|
|
+ }
|
|
|
+ let path = Path { path in
|
|
|
+ for dot in dots {
|
|
|
+ path.addEllipse(in: dot.rect)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let range = self.getGlucoseYRange(fullSize: fullSize)
|
|
|
+
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ glucoseYRange = range
|
|
|
+ announcementDots = dots
|
|
|
+ announcementPath = path
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private func calculateUnSmoothedGlucoseDots(fullSize: CGSize) {
|
|
|
calculationQueue.async {
|
|
|
let dots = glucose.concurrentMap { value -> CGRect in
|