Przeglądaj źródła

Display Remote commands (#279)

Display remote commands from Nightscout in MainChart. 
Bolus, Temp basal, Open loop, Closed loop, Pump suspend, Pump resume.
Jon B Mårtensson 2 lat temu
rodzic
commit
31456eb9d0

+ 31 - 0
FreeAPS/Resources/Assets.xcassets/owl.imageset/Contents.json

@@ -0,0 +1,31 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "light"
+        }
+      ],
+      "filename" : "apps.53525.13510798887505226.836b071a-2d62-4ce8-92bc-701910b6b96e.19f04184-2123-4d05-89b5-97f5c4f7a432-2 3.png",
+      "idiom" : "universal"
+    },
+    {
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "filename" : "apps.53525.13510798887505226.836b071a-2d62-4ce8-92bc-701910b6b96e.19f04184-2123-4d05-89b5-97f5c4f7a432-2 3 (kopia).png",
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
FreeAPS/Resources/Assets.xcassets/owl.imageset/apps.53525.13510798887505226.836b071a-2d62-4ce8-92bc-701910b6b96e.19f04184-2123-4d05-89b5-97f5c4f7a432-2 3 (kopia).png


BIN
FreeAPS/Resources/Assets.xcassets/owl.imageset/apps.53525.13510798887505226.836b071a-2d62-4ce8-92bc-701910b6b96e.19f04184-2123-4d05-89b5-97f5c4f7a432-2 3.png


+ 11 - 0
FreeAPS/Sources/APS/Storage/AnnouncementsStorage.swift

@@ -6,6 +6,7 @@ protocol AnnouncementsStorage {
     func storeAnnouncements(_ announcements: [Announcement], enacted: Bool)
     func syncDate() -> Date
     func recent() -> Announcement?
+    func validate() -> [Announcement]
 }
 
 final class BaseAnnouncementsStorage: AnnouncementsStorage, Injectable {
@@ -66,4 +67,14 @@ final class BaseAnnouncementsStorage: AnnouncementsStorage, Injectable {
         }
         return recent
     }
+
+    func validate() -> [Announcement] {
+        guard let enactedEvents = storage.retrieve(OpenAPS.FreeAPS.announcementsEnacted, as: [Announcement].self)?.reversed()
+        else {
+            return []
+        }
+        let validate = enactedEvents
+            .filter({ $0.enteredBy == Announcement.remote })
+        return validate
+    }
 }

+ 1 - 1
FreeAPS/Sources/Models/Announcement.swift

@@ -1,6 +1,6 @@
 import Foundation
 
-struct Announcement: JSON {
+struct Announcement: JSON, Equatable {
     let createdAt: Date
     let enteredBy: String
     let notes: String

+ 1 - 0
FreeAPS/Sources/Modules/Home/HomeDataFlow.swift

@@ -19,4 +19,5 @@ protocol HomeProvider: Provider {
     func pumpBattery() -> Battery?
     func pumpReservoir() -> Decimal?
     func tempTarget() -> TempTarget?
+    func announcement(_ hours: Int) -> [Announcement]
 }

+ 7 - 0
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -9,6 +9,7 @@ extension Home {
         @Injected() var pumpHistoryStorage: PumpHistoryStorage!
         @Injected() var tempTargetsStorage: TempTargetsStorage!
         @Injected() var carbsStorage: CarbsStorage!
+        @Injected() var announcementStorage: AnnouncementsStorage!
 
         var suggestion: Suggestion? {
             storage.retrieve(OpenAPS.Enact.suggested, as: Suggestion.self)
@@ -57,6 +58,12 @@ extension Home {
             }
         }
 
+        func announcement(_ hours: Int) -> [Announcement] {
+            announcementStorage.validate().filter {
+                $0.createdAt.addingTimeInterval(hours.hours.timeInterval) > Date()
+            }
+        }
+
         func pumpSettings() -> PumpSettings {
             storage.retrieve(OpenAPS.Settings.settings, as: PumpSettings.self)
                 ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))

+ 10 - 0
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -13,6 +13,7 @@ extension Home {
         private(set) var filteredHours = 24
         @Published var glucose: [BloodGlucose] = []
         @Published var isManual: [BloodGlucose] = []
+        @Published var announcement: [Announcement] = []
         @Published var suggestion: Suggestion?
         @Published var uploadStats = false
         @Published var enactedSuggestion: Suggestion?
@@ -73,6 +74,7 @@ extension Home {
             setupCarbs()
             setupBattery()
             setupReservoir()
+            setupAnnouncements()
 
             suggestion = provider.suggestion
             uploadStats = settingsManager.settings.uploadStats
@@ -308,6 +310,13 @@ extension Home {
             }
         }
 
+        private func setupAnnouncements() {
+            DispatchQueue.main.async { [weak self] in
+                guard let self = self else { return }
+                self.announcement = self.provider.announcement(self.filteredHours)
+            }
+        }
+
         private func setStatusTitle() {
             guard let suggestion = suggestion else {
                 statusTitle = "No suggestion"
@@ -423,6 +432,7 @@ extension Home.StateModel:
         setupBasals()
         setupBoluses()
         setupSuspensions()
+        setupAnnouncements()
     }
 
     func pumpSettingsDidChange(_: PumpSettings) {

+ 77 - 0
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -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

+ 1 - 0
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -389,6 +389,7 @@ extension Home {
                     tempBasals: $state.tempBasals,
                     boluses: $state.boluses,
                     suspensions: $state.suspensions,
+                    announcement: $state.announcement,
                     hours: .constant(state.filteredHours),
                     maxBasal: $state.maxBasal,
                     autotunedBasalProfile: $state.autotunedBasalProfile,