Преглед изворни кода

Refactoring + cleanup:
* Refactor parts of HomeStateModel; remove unnecessary Dispatches (all of them)
* Filter from insulin fetched, remove other unnecessary insulin-dependent objects
* Refactor HomeRootView a bit; cleanup and adjust bindings
* Refactor MainChart and clean out unnecessary functions, properties and invocations
* Highly WIP -> Current basal Rate sometimes nil; tempBasals sometimes look broken but get drawn

Deniz Cengiz пре 1 година
родитељ
комит
4969f8d430

+ 5 - 5
FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -39,7 +39,7 @@
       },
       {
         "package": "SwiftCharts",
-        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts",
+        "repositoryURL": "https://github.com/ivanschuetz/SwiftCharts.git",
         "state": {
           "branch": "master",
           "revision": "c354c1945bb35a1f01b665b22474f6db28cba4a2",
@@ -60,8 +60,8 @@
         "repositoryURL": "https://github.com/SwiftKickMobile/SwiftMessages",
         "state": {
           "branch": null,
-          "revision": "b29dd21090b708aa0ae9ecbaf6e2d0487028dc3f",
-          "version": "9.0.6"
+          "revision": "62e12e138fc3eedf88c7553dd5d98712aa119f40",
+          "version": "9.0.9"
         }
       },
       {
@@ -69,8 +69,8 @@
         "repositoryURL": "https://github.com/Swinject/Swinject",
         "state": {
           "branch": null,
-          "revision": "8bc503e60965298984fb58cf47b71c541449fe2a",
-          "version": "2.8.3"
+          "revision": "3125943807991bc271d366205d98713696d65a1f",
+          "version": "2.8.8"
         }
       }
     ]

+ 34 - 66
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -16,9 +16,6 @@ extension Home {
         @Published var announcement: [Announcement] = []
         @Published var uploadStats = false
         @Published var recentGlucose: BloodGlucose?
-        @Published var tempBasals: [PumpHistoryEvent] = []
-        @Published var boluses: [PumpHistoryEvent] = []
-        @Published var suspensions: [PumpHistoryEvent] = []
         @Published var maxBasal: Decimal = 2
         @Published var autotunedBasalProfile: [BasalProfileEntry] = []
         @Published var basalProfile: [BasalProfileEntry] = []
@@ -29,7 +26,6 @@ extension Home {
         @Published var isLooping = false
         @Published var statusTitle = ""
         @Published var lastLoopDate: Date = .distantPast
-        @Published var tempRate: Decimal?
         @Published var battery: Battery?
         @Published var reservoir: Decimal?
         @Published var pumpName = ""
@@ -74,6 +70,9 @@ extension Home {
         @Published var fpusFromPersistence: [CarbEntryStored] = []
         @Published var determinationsFromPersistence: [OrefDetermination] = []
         @Published var insulinFromPersistence: [PumpEventStored] = []
+        @Published var tempBasals: [PumpEventStored] = []
+        var boluses: [PumpEventStored] = []
+        @Published var suspensions: [PumpEventStored] = []
         @Published var batteryFromPersistence: [OpenAPS_Battery] = []
         @Published var lastPumpBolus: PumpEventStored?
 
@@ -90,9 +89,6 @@ extension Home {
             setupInsulinArray()
             setupLastBolus()
             setupBatteryArray()
-            setupBasals()
-            setupBoluses()
-            setupSuspensions()
             setupPumpSettings()
             setupBasalProfile()
             setupTempTargets()
@@ -121,7 +117,6 @@ extension Home {
             broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(DeterminationObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
-            broadcaster.register(PumpHistoryObserver.self, observer: self)
             broadcaster.register(PumpSettingsObserver.self, observer: self)
             broadcaster.register(BasalProfileObserver.self, observer: self)
             broadcaster.register(TempTargetsObserver.self, observer: self)
@@ -237,41 +232,6 @@ extension Home {
             }
         }
 
-        private func setupBasals() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.manualTempBasal = self.apsManager.isManualTempBasal
-                self.tempBasals = self.provider.pumpHistory(hours: self.filteredHours).filter {
-                    $0.type == .tempBasal || $0.type == .tempBasalDuration
-                }
-                let lastTempBasal = Array(self.tempBasals.suffix(2))
-                guard lastTempBasal.count == 2 else {
-                    self.tempRate = nil
-                    return
-                }
-
-                guard let lastRate = lastTempBasal[0].rate, let lastDuration = lastTempBasal[1].durationMin else {
-                    self.tempRate = nil
-                    return
-                }
-                let lastDate = lastTempBasal[0].timestamp
-                guard Date().timeIntervalSince(lastDate.addingTimeInterval(lastDuration.minutes.timeInterval)) < 0 else {
-                    self.tempRate = nil
-                    return
-                }
-                self.tempRate = lastRate
-            }
-        }
-
-        private func setupBoluses() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.boluses = self.provider.pumpHistory(hours: self.filteredHours).filter {
-                    $0.type == .bolus
-                }
-            }
-        }
-
         func calculateTINS() -> String {
             let date = Date()
             let calendar = Calendar.current
@@ -281,27 +241,13 @@ extension Home {
             offsetComponents.hour = -Int(offset)
             let startTime = calendar.date(byAdding: offsetComponents, to: date)!
 
-            let bolusesForCurrentDay = boluses.filter { $0.timestamp >= startTime && $0.type == .bolus }
-            let totalBolus = bolusesForCurrentDay.map { $0.amount ?? 0 }.reduce(0, +)
+            let bolusesForCurrentDay = boluses.filter { $0.timestamp ?? .distantPast >= startTime }
+            let totalBolus = bolusesForCurrentDay.map { Double($0.bolus?.amount ?? 0.0) }.reduce(0.0, +)
             roundedTotalBolus = Decimal(round(100 * Double(totalBolus)) / 100).formatted()
 
             return roundedTotalBolus
         }
 
-        private func setupSuspensions() {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.suspensions = self.provider.pumpHistory(hours: self.filteredHours).filter {
-                    $0.type == .pumpSuspend || $0.type == .pumpResume
-                }
-
-                let last = self.suspensions.last
-                let tbr = self.tempBasals.first { $0.timestamp > (last?.timestamp ?? .distantPast) }
-
-                self.pumpSuspended = tbr == nil && last?.type == .pumpSuspend
-            }
-        }
-
         private func setupPumpSettings() {
             DispatchQueue.main.async { [weak self] in
                 guard let self = self else { return }
@@ -380,7 +326,6 @@ extension Home.StateModel:
     GlucoseObserver,
     DeterminationObserver,
     SettingsObserver,
-    PumpHistoryObserver,
     PumpSettingsObserver,
     BasalProfileObserver,
     TempTargetsObserver,
@@ -412,12 +357,9 @@ extension Home.StateModel:
         tins = settingsManager.settings.tins
     }
 
-    func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
-        setupBasals()
-        setupBoluses()
-        setupSuspensions()
-        setupAnnouncements()
-    }
+//    func pumpHistoryDidUpdate(_: [PumpHistoryEvent]) {
+//        setupAnnouncements()
+//    }
 
     func pumpSettingsDidChange(_: PumpSettings) {
         setupPumpSettings()
@@ -721,6 +663,32 @@ extension Home.StateModel {
                 try viewContext.existingObject(with: id) as? PumpEventStored
             }
             insulinFromPersistence = insulinObjects
+
+            // filter tempbasals
+            manualTempBasal = apsManager.isManualTempBasal
+            tempBasals = insulinFromPersistence.filter({ $0.tempBasal != nil })
+
+            let lastTempBasal = Array(tempBasals.suffix(2))
+
+            guard lastTempBasal.count == 2 else {
+                return
+            }
+
+            guard
+                let lastTempBasalDose = lastTempBasal.first(where: { $0.tempBasal?.tempType == EventType.tempBasal.rawValue }),
+                let lastDate = lastTempBasalDose.timestamp
+            else {
+                return
+            }
+
+            // suspension and resume events
+            suspensions = insulinFromPersistence
+                .filter({ $0.type == EventType.pumpSuspend.rawValue || $0.type == EventType.pumpResume.rawValue })
+            let lastSuspension = suspensions.last
+
+            pumpSuspended = lastDate > lastSuspension?.timestamp ?? .distantPast && lastSuspension?.type == EventType.pumpSuspend
+                .rawValue
+
         } catch {
             debugPrint(
                 "Home State: \(#function) \(DebuggingIdentifiers.failed) error while updating the insulin array: \(error.localizedDescription)"

+ 25 - 75
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -18,25 +18,12 @@ private struct BasalProfile: Hashable {
     }
 }
 
-private struct Prediction: Hashable {
-    let amount: Int
-    let timestamp: Date
-    let type: PredictionType
-}
-
 private struct ChartTempTarget: Hashable {
     let amount: Decimal
     let start: Date
     let end: Date
 }
 
-private enum PredictionType: Hashable {
-    case iob
-    case cob
-    case zt
-    case uam
-}
-
 struct MainChartView: View {
     private enum Config {
         static let bolusSize: CGFloat = 5
@@ -49,9 +36,6 @@ struct MainChartView: View {
     }
 
     @Binding var units: GlucoseUnits
-    @Binding var tempBasals: [PumpHistoryEvent]
-    @Binding var boluses: [PumpHistoryEvent]
-    @Binding var suspensions: [PumpHistoryEvent]
     @Binding var announcement: [Announcement]
     @Binding var hours: Int
     @Binding var maxBasal: Decimal
@@ -70,10 +54,8 @@ struct MainChartView: View {
     @StateObject var state: Home.StateModel
 
     @State var didAppearTrigger = false
-    @State private var BasalProfiles: [BasalProfile] = []
-    @State private var TempBasals: [PumpHistoryEvent] = []
-    @State private var ChartTempTargets: [ChartTempTarget] = []
-    @State private var Predictions: [Prediction] = []
+    @State private var basalProfiles: [BasalProfile] = []
+    @State private var chartTempTargets: [ChartTempTarget] = []
     @State private var count: Decimal = 1
     @State private var startMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 86400))
     @State private var endMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 + 10800))
@@ -151,7 +133,7 @@ struct MainChartView: View {
                         updateStartEndMarkers()
                         scroller.scrollTo("MainChart", anchor: .trailing)
                     }
-                    .onChange(of: tempBasals) { _ in
+                    .onChange(of: state.tempBasals) { _ in
                         updateStartEndMarkers()
                         scroller.scrollTo("MainChart", anchor: .trailing)
                     }
@@ -224,7 +206,7 @@ extension MainChartView {
                 }
             }
             .id("MainChart")
-            .onChange(of: boluses) { _ in
+            .onChange(of: state.boluses) { _ in
                 state.roundedTotalBolus = state.calculateTINS()
             }
             .onChange(of: tempTargets) { _ in
@@ -283,23 +265,19 @@ extension MainChartView {
                 drawTempBasals()
                 drawBasalProfile()
                 drawSuspensions()
-            }.onChange(of: tempBasals) { _ in
+            }.onChange(of: state.tempBasals) { _ in
                 calculateBasals()
-                calculateTempBasals()
             }
             .onChange(of: maxBasal) { _ in
                 calculateBasals()
-                calculateTempBasals()
             }
             .onChange(of: autotunedBasalProfile) { _ in
                 calculateBasals()
-                calculateTempBasals()
             }
             .onChange(of: didAppearTrigger) { _ in
                 calculateBasals()
-                calculateTempBasals()
             }.onChange(of: basalProfile) { _ in
-                calculateTempBasals()
+                calculateBasals()
             }
             .frame(height: UIScreen.main.bounds.height * 0.08)
             .frame(width: fullWidth(viewWidth: screenSize.width))
@@ -534,7 +512,7 @@ extension MainChartView {
 
     private func drawTempTargets() -> some ChartContent {
         /// temp targets
-        ForEach(ChartTempTargets, id: \.self) { target in
+        ForEach(chartTempTargets, id: \.self) { target in
             let targetLimited = min(max(target.amount, 0), upperLimit)
 
             RuleMark(
@@ -562,19 +540,23 @@ extension MainChartView {
     }
 
     private func drawSuspensions() -> some ChartContent {
-        /// pump suspensions
-        ForEach(suspensions) { suspension in
+        let suspensions = state.suspensions
+
+        return ForEach(suspensions) { suspension in
             let now = Date()
 
-            if suspension.type == EventType.pumpSuspend {
-                let suspensionStart = suspension.timestamp
+            if let type = suspension.type, type == EventType.pumpSuspend.rawValue, let suspensionStart = suspension.timestamp {
                 let suspensionEnd = min(
-                    suspensions
-                        .first(where: { $0.timestamp > suspension.timestamp && $0.type == EventType.pumpResume })?
-                        .timestamp ?? now,
+                    (
+                        suspensions
+                            .first(where: {
+                                $0.timestamp ?? now > suspensionStart && $0.type == EventType.pumpResume.rawValue })?
+                            .timestamp
+                    ) ?? now,
                     now
                 )
-                let basalProfileDuringSuspension = BasalProfiles.first(where: { $0.startDate <= suspensionStart })
+
+                let basalProfileDuringSuspension = basalProfiles.first(where: { $0.startDate <= suspensionStart })
                 let suspensionMarkHeight = basalProfileDuringSuspension?.amount ?? 1
 
                 RectangleMark(
@@ -594,7 +576,8 @@ extension MainChartView {
             let duration = temp.tempBasal?.duration ?? 0
             let timestamp = temp.timestamp ?? Date()
             let end = min(timestamp + duration.minutes, now)
-            let isInsulinSuspended = suspensions.contains { $0.timestamp >= timestamp && $0.timestamp <= end }
+            let isInsulinSuspended = state.suspensions.contains { $0.timestamp ?? now >= timestamp && $0.timestamp ?? now <= end }
+
             let rate = Double(truncating: temp.tempBasal?.rate ?? Decimal.zero as NSDecimalNumber) * (isInsulinSuspended ? 0 : 1)
 
             // Check if there's a subsequent temp basal to determine the end time
@@ -624,7 +607,7 @@ extension MainChartView {
 
     private func drawBasalProfile() -> some ChartContent {
         /// dashed profile line
-        ForEach(BasalProfiles, id: \.self) { profile in
+        ForEach(basalProfiles, id: \.self) { profile in
             LineMark(
                 x: .value("Start Date", profile.startDate),
                 y: .value("Amount", profile.amount),
@@ -734,33 +717,7 @@ extension MainChartView {
             }
         }
 
-        ChartTempTargets = calculatedTTs
-    }
-
-    private func calculateTempBasals() {
-        let basals = tempBasals
-        var returnTempBasalRates: [PumpHistoryEvent] = []
-        var finished: [Int: Bool] = [:]
-        basals.indices.forEach { i in
-            basals.indices.forEach { j in
-                if basals[i].timestamp == basals[j].timestamp, i != j, !(finished[i] ?? false), !(finished[j] ?? false) {
-                    let rate = basals[i].rate ?? basals[j].rate
-                    let durationMin = basals[i].durationMin ?? basals[j].durationMin
-                    finished[i] = true
-                    if rate != 0 || durationMin != 0 {
-                        returnTempBasalRates.append(
-                            PumpHistoryEvent(
-                                id: basals[i].id, type: FreeAPS.EventType.tempBasal,
-                                timestamp: basals[i].timestamp,
-                                durationMin: durationMin,
-                                rate: rate
-                            )
-                        )
-                    }
-                }
-            }
-        }
-        TempBasals = returnTempBasalRates
+        chartTempTargets = calculatedTTs
     }
 
     private func findRegularBasalPoints(
@@ -852,15 +809,8 @@ extension MainChartView {
                 startDate: totalBasal[index].startDate,
                 endDate: totalBasal.count > index + 1 ? totalBasal[index + 1].startDate : endMarker
             ))
-//            print(
-//                "Basal",
-//                totalBasal[index].startDate,
-//                totalBasal.count > index + 1 ? totalBasal[index + 1].startDate : endMarker,
-//                totalBasal[index].amount,
-//                totalBasal[index].isOverwritten
-//            )
-        }
-        BasalProfiles = basals
+        }
+        basalProfiles = basals
     }
 
     // MARK: - Chart formatting

+ 48 - 69
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -36,7 +36,8 @@ extension Home {
 
         @Environment(\.managedObjectContext) var moc
         @Environment(\.colorScheme) var colorScheme
-
+        
+        // TODO: rework this stuff -> remove fetch requests; no one wants this here.
         @FetchRequest(
             entity: Override.entity(),
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
@@ -59,6 +60,8 @@ extension Home {
             sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
         ) var enactedSliderTT: FetchedResults<TempTargetsSlider>
 
+        // TODO: end todo
+        
         var bolusProgressFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
@@ -196,6 +199,50 @@ extension Home {
 
             return rateString + " " + NSLocalizedString(" U/hr", comment: "Unit per hour with space") + manualBasalString
         }
+        
+        var overrideString: String? {
+            guard let fetched = fetchedPercent.first, fetched.enabled else {
+                return nil
+            }
+
+            let percent = fetched.percentage
+            let percentString = percent == 100 ? "" : "\(percent.formatted(.number)) %"
+
+            let unit = state.units
+            var target = (fetched.target ?? 100) as Decimal
+            target = unit == .mmolL ? target.asMmolL : target
+
+            var targetString = target == 0 ? "" : (fetchedTargetFormatter.string(from: target as NSNumber) ?? "") + " " + unit
+                .rawValue
+            if tempTargetString != nil {
+                targetString = ""
+            }
+
+            let duration = fetched.duration ?? 0
+            let addedMinutes = Int(truncating: duration)
+            let date = fetched.date ?? Date()
+            let newDuration = max(
+                Decimal(Date().distance(to: date.addingTimeInterval(addedMinutes.minutes.timeInterval)).minutes),
+                0
+            )
+            let indefinite = fetched.indefinite
+            var durationString = ""
+
+            if !indefinite {
+                if newDuration >= 1 {
+                    durationString =
+                        "\(newDuration.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))) min"
+                } else if newDuration > 0 {
+                    durationString =
+                        "\((newDuration * 60).formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))) s"
+                }
+            }
+
+            let smbToggleString = fetched.smbIsOff ? " \u{20e0}" : ""
+
+            let components = [percentString, targetString, durationString, smbToggleString].filter { !$0.isEmpty }
+            return components.isEmpty ? nil : components.joined(separator: ", ")
+        }
 
         var tempTargetString: String? {
             guard let tempTarget = state.tempTarget else {
@@ -217,71 +264,6 @@ extension Home {
             return tempTarget.displayName + " " + percentString
         }
 
-        var overrideString: String? {
-            guard fetchedPercent.first?.enabled ?? false else {
-                return nil
-            }
-            var percentString = "\((fetchedPercent.first?.percentage ?? 100).formatted(.number)) %"
-            var target = (fetchedPercent.first?.target ?? 100) as Decimal
-            let indefinite = (fetchedPercent.first?.indefinite ?? false)
-            let unit = state.units.rawValue
-            if state.units == .mmolL {
-                target = target.asMmolL
-            }
-            var targetString = (fetchedTargetFormatter.string(from: target as NSNumber) ?? "") + " " + unit
-            if tempTargetString != nil || target == 0 { targetString = "" }
-            percentString = percentString == "100 %" ? "" : percentString
-
-            let duration = (fetchedPercent.first?.duration ?? 0) as Decimal
-            let addedMinutes = Int(duration)
-            let date = fetchedPercent.first?.date ?? Date()
-            var newDuration: Decimal = 0
-
-            if date.addingTimeInterval(addedMinutes.minutes.timeInterval) > Date() {
-                newDuration = Decimal(Date().distance(to: date.addingTimeInterval(addedMinutes.minutes.timeInterval)).minutes)
-            }
-
-            var durationString = indefinite ?
-                "" : newDuration >= 1 ?
-                (newDuration.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))) + " min") :
-                (
-                    newDuration > 0 ? (
-                        (newDuration * 60).formatted(.number.grouping(.never).rounded().precision(.fractionLength(0))) + " s"
-                    ) :
-                        ""
-                )
-
-            let smbToggleString = (fetchedPercent.first?.smbIsOff ?? false) ? " \u{20e0}" : ""
-            var comma1 = ", "
-            var comma2 = comma1
-            var comma3 = comma1
-            if targetString == "" || percentString == "" { comma1 = "" }
-            if durationString == "" { comma2 = "" }
-            if smbToggleString == "" { comma3 = "" }
-
-            if percentString == "", targetString == "" {
-                comma1 = ""
-                comma2 = ""
-            }
-            if percentString == "", targetString == "", smbToggleString == "" {
-                durationString = ""
-                comma1 = ""
-                comma2 = ""
-                comma3 = ""
-            }
-            if durationString == "" {
-                comma2 = ""
-            }
-            if smbToggleString == "" {
-                comma3 = ""
-            }
-
-            if durationString == "", !indefinite {
-                return nil
-            }
-            return percentString + comma1 + targetString + comma2 + durationString + comma3 + smbToggleString
-        }
-
         var infoPanel: some View {
             HStack(alignment: .center) {
                 if state.pumpSuspended {
@@ -356,9 +338,6 @@ extension Home {
 
                 MainChartView(
                     units: $state.units,
-                    tempBasals: $state.tempBasals,
-                    boluses: $state.boluses,
-                    suspensions: $state.suspensions,
                     announcement: $state.announcement,
                     hours: .constant(state.filteredHours),
                     maxBasal: $state.maxBasal,