Просмотр исходного кода

carbs required and no loop notifications

Ivan Valkou 4 лет назад
Родитель
Сommit
3139677f7f

+ 2 - 1
FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json

@@ -16,5 +16,6 @@
     "useAlarmSound": false,
     "addSourceInfoToGlucoseNotifications": false,
     "lowGlucose": 72,
-    "highGlucose": 270
+    "highGlucose": 270,
+    "carbsRequiredThreshold": 10
 }

+ 15 - 0
FreeAPS/Sources/Localizations/Main/en.lproj/Localizable.strings

@@ -825,6 +825,21 @@ Enact a temp Basal or a temp target */
 /* */
 "Also add source info" = "Also add source info";
 
+/* */
+"Carbs Requeted Threshold" = "Carbs Requeted Threshold";
+
+/* */
+"Carbs required: %d g" = "Carbs required: %d g";
+
+/* */
+"To prevent LOW required %d g of carbs" = "To prevent LOW required %d g of carbs";
+
+/* */
+"FreeAPS X not active" = "FreeAPS X not active";
+
+/* */
+"Last loop was more then %d min ago" = "Last loop was more then %d min ago";
+
 
 /* Headers for settings ------------------- */
 

+ 14 - 0
FreeAPS/Sources/Localizations/Main/ru.lproj/Localizable.strings

@@ -824,6 +824,20 @@ Enact a temp Basal or a temp target */
 /* */
 "Also add source info" = "Дополнительная информация об истонике";
 
+/* */
+"Carbs Requeted Threshold" = "Порог необходимых углеводов";
+
+/* */
+"Carbs required: %d g" = "Необходимы углеводы: %d г";
+
+/* */
+"To prevent LOW required %d g of carbs" = "Для избежания ГИПО необходимо %d г углеводов";
+
+/* */
+"FreeAPS X not active" = "FreeAPS X неактивен";
+
+/* */
+"Last loop was more then %d min ago" = "Последний цикл был более %d минут назад";
 
 
 /* Headers for settings ------------------- */

+ 5 - 0
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -20,6 +20,7 @@ struct FreeAPSSettings: JSON, Equatable {
     var addSourceInfoToGlucoseNotifications: Bool = false
     var lowGlucose: Decimal = 72
     var highGlucose: Decimal = 270
+    var carbsRequiredThreshold: Decimal = 10
 }
 
 extension FreeAPSSettings: Decodable {
@@ -107,6 +108,10 @@ extension FreeAPSSettings: Decodable {
             settings.highGlucose = highGlucose
         }
 
+        if let carbsRequiredThreshold = try? container.decode(Decimal.self, forKey: .carbsRequiredThreshold) {
+            settings.carbsRequiredThreshold = carbsRequiredThreshold
+        }
+
         self = settings
     }
 }

+ 6 - 0
FreeAPS/Sources/Modules/NotificationsConfig/NotificationsConfigStateModel.swift

@@ -8,6 +8,7 @@ extension NotificationsConfig {
         @Published var addSourceInfoToGlucoseNotifications = false
         @Published var lowGlucose: Decimal = 0
         @Published var highGlucose: Decimal = 0
+        @Published var carbsRequiredThreshold: Decimal = 0
         var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
@@ -35,6 +36,11 @@ extension NotificationsConfig {
                 guard units == .mmolL else { return $0 }
                 return $0.asMgdL
             })
+
+            subscribeSetting(
+                \.carbsRequiredThreshold,
+                on: $carbsRequiredThreshold
+            ) { carbsRequiredThreshold = $0 }
         }
     }
 }

+ 16 - 0
FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift

@@ -17,6 +17,13 @@ extension NotificationsConfig {
             return formatter
         }
 
+        private var carbsFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 0
+            return formatter
+        }
+
         var body: some View {
             Form {
                 Section(header: Text("Glucose")) {
@@ -39,6 +46,15 @@ extension NotificationsConfig {
                         Text(state.units.rawValue).foregroundColor(.secondary)
                     }
                 }
+
+                Section(header: Text("Other")) {
+                    HStack {
+                        Text("Carbs Requeted Threshold")
+                        Spacer()
+                        DecimalTextField("0", value: $state.carbsRequiredThreshold, formatter: carbsFormatter)
+                        Text("г").foregroundColor(.secondary)
+                    }
+                }
             }
             .onAppear(perform: configureView)
             .navigationBarTitle("Notifications")

+ 95 - 2
FreeAPS/Sources/Services/UserNotifiactions/UserNotificationsManager.swift

@@ -15,25 +15,40 @@ enum GlucoseSourceKey: String {
 final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, Injectable {
     private enum Identifier: String {
         case glucocoseNotification = "FreeAPS.glucoseNotification"
+        case carbsRequiredNotification = "FreeAPS.carbsRequiredNotification"
+        case noLoopFirstNotification = "FreeAPS.noLoopFirstNotification"
+        case noLoopSecondNotification = "FreeAPS.noLoopSecondNotification"
     }
 
     @Injected() private var settingsManager: SettingsManager!
     @Injected() private var broadcaster: Broadcaster!
     @Injected() private var glucoseStorage: GlucoseStorage!
+    @Injected() private var apsManager: APSManager!
     @Injected(as: FetchGlucoseManager.self) private var sourceInfoProvider: SourceInfoProvider!
 
     @Persisted(key: "UserNotificationsManager.snoozeUntilDate") private var snoozeUntilDate: Date = .distantPast
 
     private let center = UNUserNotificationCenter.current()
+    private var lifetime = Lifetime()
 
     init(resolver: Resolver) {
         super.init()
         center.delegate = self
         injectServices(resolver)
         broadcaster.register(GlucoseObserver.self, observer: self)
+        broadcaster.register(SuggestionObserver.self, observer: self)
 
         requestNotificationPermissionsIfNeeded()
         sendGlucoseNotification()
+        subscribeOnLoop()
+    }
+
+    private func subscribeOnLoop() {
+        apsManager.lastLoopDateSubject
+            .sink { [weak self] date in
+                self?.scheduleMissingLoopNotifiactions(date: date)
+            }
+            .store(in: &lifetime)
     }
 
     private func addAppBadge(glucose: Int?) {
@@ -56,6 +71,72 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
     }
 
+    private func notifyCarbsRequired(_ carbs: Int) {
+        guard Decimal(carbs) >= settingsManager.settings.carbsRequiredThreshold else { return }
+
+        ensureCanSendNotification {
+            var titles: [String] = []
+
+            let content = UNMutableNotificationContent()
+
+            if self.snoozeUntilDate > Date() {
+                titles.append(NSLocalizedString("(Snoozed)", comment: "(Snoozed)"))
+            } else {
+                content.sound = .default
+                self.playSound()
+            }
+
+            titles.append(String(format: NSLocalizedString("Carbs required: %d g", comment: "Carbs required"), carbs))
+
+            content.title = titles.joined(separator: " ")
+            content.body = String(
+                format: NSLocalizedString(
+                    "To prevent LOW required %d g of carbs",
+                    comment: "To prevent LOW required %d g of carbs"
+                ),
+                carbs
+            )
+
+            self.addRequest(identifier: .carbsRequiredNotification, content: content, deleteOld: true)
+        }
+    }
+
+    private func scheduleMissingLoopNotifiactions(date _: Date) {
+        ensureCanSendNotification {
+            let title = NSLocalizedString("FreeAPS X not active", comment: "FreeAPS X not active")
+            let body = NSLocalizedString("Last loop was more then %d min ago", comment: "Last loop was more then %d min ago")
+
+            let firstInterval = 20 // min
+            let secondInterval = 40 // min
+
+            let firstContent = UNMutableNotificationContent()
+            firstContent.title = title
+            firstContent.body = String(format: body, firstInterval)
+            firstContent.sound = .default
+
+            let secondContent = UNMutableNotificationContent()
+            secondContent.title = title
+            secondContent.body = String(format: body, secondInterval)
+            secondContent.sound = .default
+
+            let firstTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 60 * TimeInterval(firstInterval), repeats: false)
+            let secondTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 60 * TimeInterval(secondInterval), repeats: false)
+
+            self.addRequest(
+                identifier: .noLoopFirstNotification,
+                content: firstContent,
+                deleteOld: true,
+                trigger: firstTrigger
+            )
+            self.addRequest(
+                identifier: .noLoopSecondNotification,
+                content: secondContent,
+                deleteOld: true,
+                trigger: secondTrigger
+            )
+        }
+    }
+
     private func sendGlucoseNotification() {
         addAppBadge(glucose: nil)
 
@@ -200,8 +281,13 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
     }
 
-    private func addRequest(identifier: Identifier, content: UNMutableNotificationContent, deleteOld: Bool = false) {
-        let request = UNNotificationRequest(identifier: identifier.rawValue, content: content, trigger: nil)
+    private func addRequest(
+        identifier: Identifier,
+        content: UNMutableNotificationContent,
+        deleteOld: Bool = false,
+        trigger: UNNotificationTrigger? = nil
+    ) {
+        let request = UNNotificationRequest(identifier: identifier.rawValue, content: content, trigger: trigger)
 
         if deleteOld {
             center.removeDeliveredNotifications(withIdentifiers: [identifier.rawValue])
@@ -269,6 +355,13 @@ extension BaseUserNotificationsManager: GlucoseObserver {
     }
 }
 
+extension BaseUserNotificationsManager: SuggestionObserver {
+    func suggestionDidUpdate(_ suggestion: Suggestion) {
+        guard let carndRequired = suggestion.carbsReq else { return }
+        notifyCarbsRequired(Int(carndRequired))
+    }
+}
+
 extension BaseUserNotificationsManager: UNUserNotificationCenterDelegate {
     func userNotificationCenter(
         _: UNUserNotificationCenter,