Ivan Valkou 5 лет назад
Родитель
Сommit
8aa5ef138d

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -150,6 +150,7 @@
 		388E5A5C25B6F0770019842D /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5B25B6F0770019842D /* JSON.swift */; };
 		388E5A5C25B6F0770019842D /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5B25B6F0770019842D /* JSON.swift */; };
 		388E5A6025B6F2310019842D /* Autosens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5F25B6F2310019842D /* Autosens.swift */; };
 		388E5A6025B6F2310019842D /* Autosens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388E5A5F25B6F2310019842D /* Autosens.swift */; };
 		389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389442CA25F65F7100FA1F27 /* NightscoutTreatment.swift */; };
 		389442CB25F65F7100FA1F27 /* NightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389442CA25F65F7100FA1F27 /* NightscoutTreatment.swift */; };
+		3894873A2614928B004DF424 /* DispatchTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389487392614928B004DF424 /* DispatchTimer.swift */; };
 		3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3895E4C525B9E00D00214B37 /* Preferences.swift */; };
 		3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3895E4C525B9E00D00214B37 /* Preferences.swift */; };
 		389A572026079BAA00BC102F /* Interpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389A571F26079BAA00BC102F /* Interpolation.swift */; };
 		389A572026079BAA00BC102F /* Interpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389A571F26079BAA00BC102F /* Interpolation.swift */; };
 		389ECDFE2601061500D86C4F /* View+Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389ECDFD2601061500D86C4F /* View+Snapshot.swift */; };
 		389ECDFE2601061500D86C4F /* View+Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389ECDFD2601061500D86C4F /* View+Snapshot.swift */; };
@@ -426,6 +427,7 @@
 		388E5A5B25B6F0770019842D /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
 		388E5A5B25B6F0770019842D /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
 		388E5A5F25B6F2310019842D /* Autosens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Autosens.swift; sourceTree = "<group>"; };
 		388E5A5F25B6F2310019842D /* Autosens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Autosens.swift; sourceTree = "<group>"; };
 		389442CA25F65F7100FA1F27 /* NightscoutTreatment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutTreatment.swift; sourceTree = "<group>"; };
 		389442CA25F65F7100FA1F27 /* NightscoutTreatment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutTreatment.swift; sourceTree = "<group>"; };
+		389487392614928B004DF424 /* DispatchTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchTimer.swift; sourceTree = "<group>"; };
 		3895E4C525B9E00D00214B37 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
 		3895E4C525B9E00D00214B37 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
 		389A571F26079BAA00BC102F /* Interpolation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Interpolation.swift; sourceTree = "<group>"; };
 		389A571F26079BAA00BC102F /* Interpolation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Interpolation.swift; sourceTree = "<group>"; };
 		389ECDFD2601061500D86C4F /* View+Snapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Snapshot.swift"; sourceTree = "<group>"; };
 		389ECDFD2601061500D86C4F /* View+Snapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Snapshot.swift"; sourceTree = "<group>"; };
@@ -1051,6 +1053,7 @@
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 		388E5A5A25B6F05F0019842D /* Helpers */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				389487392614928B004DF424 /* DispatchTimer.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				38F37827261260DC009DB701 /* Color+Extensions.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
 				389ECE042601144100D86C4F /* ConcurrentMap.swift */,
 				3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */,
 				3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */,
@@ -1607,6 +1610,7 @@
 				38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */,
 				38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */,
 				3811DEC225C9D99900A708ED /* SecurityContainer.swift in Sources */,
 				3811DEC225C9D99900A708ED /* SecurityContainer.swift in Sources */,
 				38AEE75225F022080013F05B /* SettingsManager.swift in Sources */,
 				38AEE75225F022080013F05B /* SettingsManager.swift in Sources */,
+				3894873A2614928B004DF424 /* DispatchTimer.swift in Sources */,
 				38B4F3CF25E5041600E76A18 /* APSContainer.swift in Sources */,
 				38B4F3CF25E5041600E76A18 /* APSContainer.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
 				3811DE6E25C9D62600A708ED /* OnboardingViewModel.swift in Sources */,
 				3811DE6E25C9D62600A708ED /* OnboardingViewModel.swift in Sources */,

+ 1 - 1
FreeAPS/Resources/Info.plist

@@ -17,7 +17,7 @@
 	<key>CFBundleShortVersionString</key>
 	<key>CFBundleShortVersionString</key>
 	<string>$(MARKETING_VERSION)</string>
 	<string>$(MARKETING_VERSION)</string>
 	<key>CFBundleVersion</key>
 	<key>CFBundleVersion</key>
-	<string>241</string>
+	<string>242</string>
 	<key>LSApplicationQueriesSchemes</key>
 	<key>LSApplicationQueriesSchemes</key>
 	<array>
 	<array>
 		<string>dexcomg6</string>
 		<string>dexcomg6</string>

+ 3 - 2
FreeAPS/Sources/APS/GlucoseManager.swift

@@ -12,7 +12,7 @@ final class BaseGlucoseManager: GlucoseManager, Injectable {
     @Injected() var apsManager: APSManager!
     @Injected() var apsManager: APSManager!
 
 
     private var lifetime = Set<AnyCancellable>()
     private var lifetime = Set<AnyCancellable>()
-    private let timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect()
+    private let timer = DispatchTimer(timeInterval: 10)
 
 
     init(resolver: Resolver) {
     init(resolver: Resolver) {
         injectServices(resolver)
         injectServices(resolver)
@@ -20,7 +20,7 @@ final class BaseGlucoseManager: GlucoseManager, Injectable {
     }
     }
 
 
     private func subscribe() {
     private func subscribe() {
-        timer
+        timer.publisher
             .receive(on: processQueue)
             .receive(on: processQueue)
             .flatMap { date -> AnyPublisher<[BloodGlucose], Never> in
             .flatMap { date -> AnyPublisher<[BloodGlucose], Never> in
                 guard self.glucoseStogare.syncDate().timeIntervalSince1970 + 4.minutes.timeInterval <= date.timeIntervalSince1970
                 guard self.glucoseStogare.syncDate().timeIntervalSince1970 + 4.minutes.timeInterval <= date.timeIntervalSince1970
@@ -35,5 +35,6 @@ final class BaseGlucoseManager: GlucoseManager, Injectable {
                 }
                 }
             }
             }
             .store(in: &lifetime)
             .store(in: &lifetime)
+        timer.resume()
     }
     }
 }
 }

+ 73 - 0
FreeAPS/Sources/Helpers/DispatchTimer.swift

@@ -0,0 +1,73 @@
+import Combine
+import Foundation
+
+class DispatchTimer {
+    let timeInterval: TimeInterval
+    let queue: DispatchQueue
+
+    private let subject = PassthroughSubject<Date, Never>()
+
+    init(
+        timeInterval: TimeInterval,
+        queue: DispatchQueue = DispatchQueue.markedQueue(label: "DispatchTimer.queue", qos: .userInteractive)
+    ) {
+        self.timeInterval = timeInterval
+        self.queue = queue
+    }
+
+    private lazy var timer: DispatchSourceTimer = {
+        let timer = DispatchSource.makeTimerSource(queue: queue)
+        timer.schedule(deadline: .now() + timeInterval, repeating: timeInterval)
+        timer.setEventHandler(handler: { [weak self] in
+            self?.fireEvent()
+        })
+        return timer
+    }()
+
+    private func fireEvent() {
+        subject.send(Date())
+        eventHandler?()
+    }
+
+    var eventHandler: (() -> Void)?
+
+    private enum State {
+        case suspended
+        case resumed
+    }
+
+    private var state: State = .suspended
+
+    func resume() {
+        if state == .resumed {
+            return
+        }
+        state = .resumed
+        timer.resume()
+    }
+
+    func suspend() {
+        if state == .suspended {
+            return
+        }
+        state = .suspended
+        timer.suspend()
+    }
+
+    var publisher: AnyPublisher<Date, Never> {
+        subject.eraseToAnyPublisher()
+    }
+
+    deinit {
+        timer.setEventHandler {}
+        timer.cancel()
+        /*
+         If the timer is suspended, calling cancel without resuming
+         triggers a crash. This is documented here
+         https://forums.developer.apple.com/thread/15902
+         */
+        resume()
+        eventHandler = nil
+        subject.send(completion: .finished)
+    }
+}

+ 6 - 5
FreeAPS/Sources/Modules/Home/HomeViewModel.swift

@@ -8,7 +8,7 @@ extension Home {
         @Injected() var settingsManager: SettingsManager!
         @Injected() var settingsManager: SettingsManager!
         @Injected() var apsManager: APSManager!
         @Injected() var apsManager: APSManager!
         @Injected() var nightscoutManager: NightscoutManager!
         @Injected() var nightscoutManager: NightscoutManager!
-        private let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
+        private let timer = DispatchTimer(timeInterval: 5)
         private(set) var filteredHours = 24
         private(set) var filteredHours = 24
 
 
         @Published var glucose: [BloodGlucose] = []
         @Published var glucose: [BloodGlucose] = []
@@ -78,12 +78,13 @@ extension Home {
             broadcaster.register(PumpBatteryObserver.self, observer: self)
             broadcaster.register(PumpBatteryObserver.self, observer: self)
             broadcaster.register(PumpReservoirObserver.self, observer: self)
             broadcaster.register(PumpReservoirObserver.self, observer: self)
 
 
-            timer
-                .sink { date in
-                    self.timerDate = date
+            timer.eventHandler = {
+                DispatchQueue.main.async {
+                    self.timerDate = Date()
                     self.tempTargetName = self.provider.tempTarget()?.name
                     self.tempTargetName = self.provider.tempTarget()?.name
                 }
                 }
-                .store(in: &lifetime)
+            }
+            timer.resume()
 
 
             apsManager.isLooping
             apsManager.isLooping
                 .receive(on: DispatchQueue.main)
                 .receive(on: DispatchQueue.main)

+ 17 - 18
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -55,6 +55,7 @@ struct MainChartView: View {
     @State private var carbsDots: [DotInfo] = []
     @State private var carbsDots: [DotInfo] = []
     @State private var carbsPath = Path()
     @State private var carbsPath = Path()
     @State private var glucoseYGange: GlucoseYRange = (0, 0, 0, 0)
     @State private var glucoseYGange: GlucoseYRange = (0, 0, 0, 0)
+    @State private var offset: CGFloat = 0
 
 
     private let calculationQueue = DispatchQueue(label: "MainChartView.calculationQueue")
     private let calculationQueue = DispatchQueue(label: "MainChartView.calculationQueue")
 
 
@@ -232,10 +233,10 @@ struct MainChartView: View {
         }
         }
         .fill(Color.loopGreen)
         .fill(Color.loopGreen)
         .onChange(of: glucose) { _ in
         .onChange(of: glucose) { _ in
-            calculateGlucoseDots(fullSize: fullSize)
+            update(fullSize: fullSize)
         }
         }
         .onChange(of: didAppearTrigger) { _ in
         .onChange(of: didAppearTrigger) { _ in
-            calculateGlucoseDots(fullSize: fullSize)
+            update(fullSize: fullSize)
         }
         }
     }
     }
 
 
@@ -326,24 +327,10 @@ struct MainChartView: View {
             }.fill(Color.uam)
             }.fill(Color.uam)
         }
         }
         .onChange(of: suggestion) { _ in
         .onChange(of: suggestion) { _ in
-            calculatePredictionDots(fullSize: fullSize, type: .iob)
-            calculatePredictionDots(fullSize: fullSize, type: .cob)
-            calculatePredictionDots(fullSize: fullSize, type: .zt)
-            calculatePredictionDots(fullSize: fullSize, type: .uam)
-            calculateGlucoseDots(fullSize: fullSize)
-            calculateBolusDots(fullSize: fullSize)
-            calculateCarbsDots(fullSize: fullSize)
-            calculateTempTargetsRects(fullSize: fullSize)
+            update(fullSize: fullSize)
         }
         }
         .onChange(of: didAppearTrigger) { _ in
         .onChange(of: didAppearTrigger) { _ in
-            calculatePredictionDots(fullSize: fullSize, type: .iob)
-            calculatePredictionDots(fullSize: fullSize, type: .cob)
-            calculatePredictionDots(fullSize: fullSize, type: .zt)
-            calculatePredictionDots(fullSize: fullSize, type: .uam)
-            calculateGlucoseDots(fullSize: fullSize)
-            calculateBolusDots(fullSize: fullSize)
-            calculateCarbsDots(fullSize: fullSize)
-            calculateTempTargetsRects(fullSize: fullSize)
+            update(fullSize: fullSize)
         }
         }
     }
     }
 }
 }
@@ -351,6 +338,18 @@ struct MainChartView: View {
 // MARK: - Calculations
 // MARK: - Calculations
 
 
 extension MainChartView {
 extension MainChartView {
+    private func update(fullSize: CGSize) {
+        calculatePredictionDots(fullSize: fullSize, type: .iob)
+        calculatePredictionDots(fullSize: fullSize, type: .cob)
+        calculatePredictionDots(fullSize: fullSize, type: .zt)
+        calculatePredictionDots(fullSize: fullSize, type: .uam)
+        calculateGlucoseDots(fullSize: fullSize)
+        calculateBolusDots(fullSize: fullSize)
+        calculateCarbsDots(fullSize: fullSize)
+        calculateTempTargetsRects(fullSize: fullSize)
+        calculateTempTargetsRects(fullSize: fullSize)
+    }
+
     private func calculateGlucoseDots(fullSize: CGSize) {
     private func calculateGlucoseDots(fullSize: CGSize) {
         calculationQueue.async {
         calculationQueue.async {
             let dots = glucose.concurrentMap { value -> CGRect in
             let dots = glucose.concurrentMap { value -> CGRect in