Explorar o código

Low, high alerts

Ivan Valkou %!s(int64=4) %!d(string=hai) anos
pai
achega
1ca620a336

+ 0 - 97
Dependencies/LibreTransmitter/Sources/LibreTransmitter/Common/NotificationHelper.swift

@@ -6,7 +6,6 @@
 //  Copyright © 2019 Bjørn Inge Berg. All rights reserved.
 //
 
-import AudioToolbox
 import Foundation
 import HealthKit
 import UserNotifications
@@ -32,22 +31,6 @@ public enum NotificationHelper {
         case restoredState = "no.bjorninge.miaomiao.state-notification"
     }
 
-    public static func playSoundIfNeeded(count: Int = 3) {
-        if UserDefaults.standard.mmGlucoseAlarmsVibrate {
-            playSound(times: count)
-        }
-    }
-
-    private static func playSound(times: Int) {
-        guard times > 0 else {
-            return
-        }
-
-        AudioServicesPlaySystemSoundWithCompletion(1336) {
-            playSound(times: times - 1)
-        }
-    }
-
     public static func GlucoseUnitIsSupported(unit: HKUnit) -> Bool {
         [HKUnit.milligramsPerDeciliter, HKUnit.millimolesPerLiter].contains(unit)
     }
@@ -149,34 +132,6 @@ public enum NotificationHelper {
         }
     }
 
-    private static var glucoseNotifyCalledCount = 0
-
-    public static func sendGlucoseNotitifcationIfNeeded(glucose: LibreGlucose, oldValue: LibreGlucose?, trend: GlucoseTrend?, battery: String?) {
-        glucoseNotifyCalledCount &+= 1
-
-        let shouldSendGlucoseAlternatingTimes = glucoseNotifyCalledCount != 0 && UserDefaults.standard.mmNotifyEveryXTimes != 0
-
-        let shouldSend = UserDefaults.standard.mmAlwaysDisplayGlucose || glucoseNotifyCalledCount == 1 || (shouldSendGlucoseAlternatingTimes && glucoseNotifyCalledCount % UserDefaults.standard.mmNotifyEveryXTimes == 0)
-
-        let schedules = UserDefaults.standard.glucoseSchedules
-
-        let alarm = schedules?.getActiveAlarms(glucose.glucoseDouble) ?? .none
-        let isSnoozed = GlucoseScheduleList.isSnoozed()
-
-        let transmitterBattery = UserDefaults.standard.mmShowTransmitterBattery && battery != nil ? battery : nil
-
-        logger.debug("dabear:: glucose alarmtype is \(String(describing:alarm))")
-        // We always send glucose notifications when alarm is active,
-        // even if glucose notifications are disabled in the UI
-
-        if shouldSend || alarm.isAlarming() {
-            sendGlucoseNotitifcation(glucose: glucose, oldValue: oldValue, alarm: alarm, isSnoozed: isSnoozed, trend: trend, transmitterBattery: transmitterBattery)
-        } else {
-            logger.debug("dabear:: not sending glucose, shouldSend and alarmIsActive was false")
-            return
-        }
-    }
-
     private static func addRequest(identifier: Identifiers, content: UNMutableNotificationContent, deleteOld: Bool = false) {
         let center = UNUserNotificationCenter.current()
         //content.sound = UNNotificationSound.
@@ -197,58 +152,6 @@ public enum NotificationHelper {
             logger.debug("dabear:: sending \(identifier.rawValue) notification")
         }
     }
-    private static func sendGlucoseNotitifcation(glucose: LibreGlucose, oldValue: LibreGlucose?, alarm: GlucoseScheduleAlarmResult = .none, isSnoozed: Bool = false, trend: GlucoseTrend?, transmitterBattery: String?) {
-        ensureCanSendGlucoseNotification { _ in
-            let content = UNMutableNotificationContent()
-            let glucoseDesc = glucose.description
-            var titles = [String]()
-            var body = [String]()
-            var body2 = [String]()
-            switch alarm {
-            case .none:
-                titles.append(LocalizedString("Glucose", comment: "Glucose"))
-            case .low:
-                titles.append(LocalizedString("LOWALERT!", comment: "LOWALERT!"))
-            case .high:
-                titles.append(LocalizedString("HIGHALERT!", comment: "HIGHALERT!"))
-            }
-
-            if isSnoozed {
-                titles.append(NSLocalizedString("(Snoozed)", comment: "(Snoozed)"))
-            } else if alarm.isAlarming() {
-                content.sound = .default
-                content.userInfo = ["action": "snooze"]
-                playSoundIfNeeded()
-            }
-            titles.append(glucoseDesc)
-
-            body.append(String(format: NSLocalizedString("Glucose: %@", comment: "Glucose: %@"), glucoseDesc))
-
-            if let oldValue = oldValue {
-                body.append( LibreGlucose.glucoseDiffDesc(oldValue: oldValue, newValue: glucose))
-            }
-
-            if let trendSymbol = trend?.symbol {
-                body.append("\(trendSymbol)")
-            }
-
-            if let transmitterBattery = transmitterBattery {
-                body2.append(String(format: NSLocalizedString("Transmitter: %@%%", comment: "Transmitter: %@%%"), transmitterBattery))
-            }
-
-            //these are texts that naturally fit on their own line in the body
-            var body2s = ""
-            if !body2.isEmpty {
-                body2s = "\n" + body2.joined(separator: "\n")
-            }
-
-            content.title = titles.joined(separator: " ")
-            content.body = body.joined(separator: ", ") + body2s
-//            addRequest(identifier: .glucocoseNotifications,
-//                       content: content,
-//                       deleteOld: true)
-        }
-    }
 
     public enum CalibrationMessage: String {
         case starting = "Calibrating sensor, please stand by!"

+ 1 - 42
Dependencies/LibreTransmitter/Sources/LibreTransmitter/Common/Settings/UserDefaults+Alarmsettings.swift

@@ -12,19 +12,13 @@ import HealthKit
 extension UserDefaults {
     private enum Key: String {
         case glucoseSchedules = "no.bjorninge.glucoseschedules"
-
-        case mmAlwaysDisplayGlucose = "no.bjorninge.mmAlwaysDisplayGlucose"
-        case mmNotifyEveryXTimes = "no.bjorninge.mmNotifyEveryXTimes"
-        case mmGlucoseAlarmsVibrate = "no.bjorninge.mmGlucoseAlarmsVibrate"
         case mmAlertLowBatteryWarning = "no.bjorninge.mmLowBatteryWarning"
         case mmAlertInvalidSensorDetected = "no.bjorninge.mmInvalidSensorDetected"
-        //case mmAlertalarmNotifications
         case mmAlertNewSensorDetected = "no.bjorninge.mmNewSensorDetected"
         case mmAlertNoSensorDetected = "no.bjorninge.mmNoSensorDetected"
         case mmGlucoseUnit = "no.bjorninge.mmGlucoseUnit"
         case mmAlertSensorSoonExpire = "no.bjorninge.mmAlertSensorSoonExpire"
         case mmSnoozedUntil = "no.bjorninge.mmSnoozedUntil"
-        case mmShowTransmitterBattery = "no.bjorninge.mmShowTransmitterBattery"
     }
     /*
      case always
@@ -42,23 +36,6 @@ extension UserDefaults {
         return nil
     }
 
-    var mmAlwaysDisplayGlucose: Bool {
-        get {
-            optionalBool(forKey: Key.mmAlwaysDisplayGlucose.rawValue) ?? true
-        }
-        set {
-            set(newValue, forKey: Key.mmAlwaysDisplayGlucose.rawValue)
-        }
-    }
-    var mmNotifyEveryXTimes: Int {
-        get {
-            integer(forKey: Key.mmNotifyEveryXTimes.rawValue)
-        }
-        set {
-            set(newValue, forKey: Key.mmNotifyEveryXTimes.rawValue)
-        }
-    }
-
     var mmAlertLowBatteryWarning: Bool {
         get {
             optionalBool(forKey: Key.mmAlertLowBatteryWarning.rawValue) ?? true
@@ -103,26 +80,8 @@ extension UserDefaults {
         }
     }
 
-    var mmGlucoseAlarmsVibrate: Bool {
-        get {
-            optionalBool(forKey: Key.mmGlucoseAlarmsVibrate.rawValue) ?? true
-        }
-        set {
-            set(newValue, forKey: Key.mmGlucoseAlarmsVibrate.rawValue)
-        }
-    }
-
-    var mmShowTransmitterBattery: Bool {
-        get {
-            optionalBool(forKey: Key.mmShowTransmitterBattery.rawValue) ?? true
-        }
-        set {
-            set(newValue, forKey: Key.mmShowTransmitterBattery.rawValue)
-        }
-    }
-
     var allNotificationToggles: [Bool] {
-        [mmAlwaysDisplayGlucose, mmAlertLowBatteryWarning, mmAlertInvalidSensorDetected, mmAlertNewSensorDetected, mmAlertNoSensorDetected, mmAlertWillSoonExpire, mmGlucoseAlarmsVibrate, mmShowTransmitterBattery]
+        [mmAlertLowBatteryWarning, mmAlertInvalidSensorDetected, mmAlertNewSensorDetected, mmAlertNoSensorDetected, mmAlertWillSoonExpire]
     }
 
     //intentionally only supports mgdl and mmol

+ 0 - 26
Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterManager.swift

@@ -110,9 +110,6 @@ public final class LibreTransmitterManager: LibreTransmitterDelegate {
 
     public private(set) var lastConnected: Date?
 
-    public private(set) var alarmStatus = AlarmStatus()
-
-
     public private(set) var latestPrediction: LibreGlucose?
     public private(set) var latestBackfill: LibreGlucose? {
         willSet(newValue) {
@@ -123,29 +120,6 @@ public final class LibreTransmitterManager: LibreTransmitterDelegate {
             var trend: GlucoseTrend?
             let oldValue = latestBackfill
 
-            defer {
-                logger.debug("dabear:: sending glucose notification")
-                NotificationHelper.sendGlucoseNotitifcationIfNeeded(glucose: newValue,
-                                                                    oldValue: oldValue,
-                                                                    trend: trend,
-                                                                    battery: batteryString)
-
-                //once we have a new glucose value, we can update the isalarming property
-                if let activeAlarms = UserDefaults.standard.glucoseSchedules?.getActiveAlarms(newValue.glucoseDouble) {
-                    DispatchQueue.main.async {
-                        self.alarmStatus.isAlarming = ([.high,.low].contains(activeAlarms))
-                        self.alarmStatus.glucoseScheduleAlarmResult = activeAlarms
-                    }
-                } else {
-                    DispatchQueue.main.async {
-                    self.alarmStatus.isAlarming = false
-                    self.alarmStatus.glucoseScheduleAlarmResult = .none
-                    }
-                }
-
-
-            }
-
             logger.debug("dabear:: latestBackfill set, newvalue is \(newValue.description)")
 
             if let oldValue = oldValue {

+ 1 - 2
Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterUI/LibreTransmitterManager+UI.swift

@@ -81,8 +81,7 @@ public struct LibreTransmitterSettingsView: UIViewControllerRepresentable {
             notifyDelete: wantToTerminateNotifier,
             transmitterInfoObservable: manager.transmitterInfoObservable,
             sensorInfoObervable: manager.sensorInfoObservable,
-            glucoseInfoObservable: manager.glucoseInfoObservable,
-            alarmStatus: manager.alarmStatus
+            glucoseInfoObservable: manager.glucoseInfoObservable
         )
 
         let nav = SettingsNavigationViewController(rootViewController: settings)

+ 0 - 43
Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterUI/Views/Settings/NotificationSettingsView.swift

@@ -36,11 +36,6 @@ struct NotificationSettingsView: View {
 
 
     private enum Key: String {
-        //case glucoseSchedules = "no.bjorninge.glucoseschedules"
-
-        case mmAlwaysDisplayGlucose = "no.bjorninge.mmAlwaysDisplayGlucose"
-        case mmNotifyEveryXTimes = "no.bjorninge.mmNotifyEveryXTimes"
-        case mmGlucoseAlarmsVibrate = "no.bjorninge.mmGlucoseAlarmsVibrate"
         case mmAlertLowBatteryWarning = "no.bjorninge.mmLowBatteryWarning"
         case mmAlertInvalidSensorDetected = "no.bjorninge.mmInvalidSensorDetected"
         //case mmAlertalarmNotifications
@@ -49,30 +44,16 @@ struct NotificationSettingsView: View {
 
         case mmAlertSensorSoonExpire = "no.bjorninge.mmAlertSensorSoonExpire"
 
-        case mmShowTransmitterBattery = "no.bjorninge.mmShowTransmitterBattery"
-
         //handle specially:
         case mmGlucoseUnit = "no.bjorninge.mmGlucoseUnit"
     }
 
-
-
-
-
-    @AppStorage(Key.mmAlwaysDisplayGlucose.rawValue) var mmAlwaysDisplayGlucose: Bool = true
-    @AppStorage(Key.mmNotifyEveryXTimes.rawValue) var mmNotifyEveryXTimes: Int = 0
-    @AppStorage(Key.mmShowTransmitterBattery.rawValue) var mmShowTransmitterBattery: Bool = true
-
-
-
     @AppStorage(Key.mmAlertLowBatteryWarning.rawValue) var mmAlertLowBatteryWarning: Bool = true
     @AppStorage(Key.mmAlertInvalidSensorDetected.rawValue) var mmAlertInvalidSensorDetected: Bool = true
     @AppStorage(Key.mmAlertNewSensorDetected.rawValue) var mmAlertNewSensorDetected: Bool = true
     @AppStorage(Key.mmAlertNoSensorDetected.rawValue) var mmAlertNoSensorDetected: Bool = true
     @AppStorage(Key.mmAlertSensorSoonExpire.rawValue) var mmAlertSensorSoonExpire: Bool = true
 
-    @AppStorage(Key.mmGlucoseAlarmsVibrate.rawValue) var mmGlucoseAlarmsVibrate: Bool = true
-
     //especially handled mostly for backward compat
     @AppStorage(Key.mmGlucoseUnit.rawValue) var mmGlucoseUnit: String = ""
 
@@ -83,28 +64,6 @@ struct NotificationSettingsView: View {
 
     static let formatter = NumberFormatter()
 
-    var glucoseVisibilitySection : some View {
-        Section(header: Text("Glucose Notification visibility") ) {
-            Toggle("Always Notify Glucose", isOn: $mmAlwaysDisplayGlucose)
-
-            HStack {
-                Text("Notify per reading")
-                TextField("", value: $mmNotifyEveryXTimes, formatter: Self.formatter)
-                    .multilineTextAlignment(.center)
-                    .disabled(true)
-                    .frame(minWidth: 15, maxWidth: 60)
-                    .textFieldStyle(RoundedBorderTextFieldStyle())
-                Stepper("Value", value: $mmNotifyEveryXTimes, in: 0...9)
-                    .labelsHidden()
-
-            }.clipped()
-
-            Toggle("Adds Transmitter Battery", isOn: $mmShowTransmitterBattery)
-            Toggle("Also play alert sound", isOn: $mmGlucoseAlarmsVibrate)
-
-        }
-    }
-
     var additionalNotificationsSection : some View {
         Section(header: Text("Additional notification types")) {
             Toggle("Low battery", isOn:$mmAlertLowBatteryWarning)
@@ -132,8 +91,6 @@ struct NotificationSettingsView: View {
 
     var body: some View {
         List {
-            
-            glucoseVisibilitySection
             additionalNotificationsSection
 
             miscSection

+ 3 - 14
Dependencies/LibreTransmitter/Sources/LibreTransmitter/LibreTransmitterUI/Views/Settings/SettingsView.swift

@@ -107,7 +107,6 @@ struct SettingsView: View {
     //most of the settings are now retrieved from the cgmmanager observables instead
     @StateObject var model = SettingsModel()
     @State private var presentableStatus: StatusMessage?
-    @ObservedObject var alarmStatus: LibreTransmitter.AlarmStatus
 
     @State private var showingDestructQuestion = false
     @State private var showingExporter = false
@@ -121,11 +120,10 @@ struct SettingsView: View {
         notifyDelete: GenericObservableObject,
         transmitterInfoObservable:LibreTransmitter.TransmitterInfo,
         sensorInfoObervable: LibreTransmitter.SensorInfo,
-        glucoseInfoObservable: LibreTransmitter.GlucoseInfo,
-        alarmStatus: LibreTransmitter.AlarmStatus) -> UIHostingController<SettingsView> {
+        glucoseInfoObservable: LibreTransmitter.GlucoseInfo) -> UIHostingController<SettingsView> {
         UIHostingController(rootView: self.init(
             //displayGlucoseUnitObservable: displayGlucoseUnitObservable,
-            transmitterInfo: transmitterInfoObservable, sensorInfo: sensorInfoObervable, glucoseMeasurement: glucoseInfoObservable, notifyComplete: notifyComplete, notifyDelete: notifyDelete, alarmStatus: alarmStatus, glucoseUnit: glucoseUnit
+            transmitterInfo: transmitterInfoObservable, sensorInfo: sensorInfoObervable, glucoseMeasurement: glucoseInfoObservable, notifyComplete: notifyComplete, notifyDelete: notifyDelete, glucoseUnit: glucoseUnit
 
         ))
     }
@@ -185,19 +183,10 @@ struct SettingsView: View {
 
     var snoozeSection: some View {
         Section {
-            NavigationLink(destination: SnoozeView(isAlarming: $alarmStatus.isAlarming, activeAlarms: $alarmStatus.glucoseScheduleAlarmResult)) {
-                if alarmStatus.isAlarming {
-                    Text("Snooze Alerts").frame(alignment: .center)
-                        .padding(.top, 30)
-                        .padding(.bottom, 30)
-                } else {
-                    Text("Snooze Alerts").frame(alignment: .center)
-                }
-            }
+            Text("Snooze Alerts").frame(alignment: .center)
         }
     }
 
-
     var measurementSection : some View {
         Section(header: Text("Last measurement")) {
             if glucoseUnit == .millimolesPerLiter {

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

@@ -12,5 +12,9 @@
     "cgm": "nightscout",
     "uploadGlucose": false,
     "glucoseBadge": false,
-    "glucoseNotificationsAlways": false
+    "glucoseNotificationsAlways": false,
+    "useAlarmSound": false,
+    "addSourceInfoToGlucoseNotifications": false,
+    "lowGlucose": 72,
+    "highGlucose": 270
 }

+ 3 - 2
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -18,6 +18,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
     private let processQueue = DispatchQueue(label: "BaseGlucoseStorage.processQueue")
     @Injected() private var storage: FileStorage!
     @Injected() private var broadcaster: Broadcaster!
+    @Injected() private var settingsManager: SettingsManager!
 
     private enum Config {
         static let filterTime: TimeInterval = 4.5 * 60
@@ -106,11 +107,11 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
         guard let glucose = recent().last, glucose.dateString.addingTimeInterval(20.minutes.timeInterval) > Date(),
               let glucoseValue = glucose.glucose else { return nil }
 
-        if glucoseValue < 72 {
+        if Decimal(glucoseValue) < settingsManager.settings.lowGlucose {
             return .low
         }
 
-        if glucoseValue > 270 {
+        if Decimal(glucoseValue) > settingsManager.settings.highGlucose {
             return .high
         }
 

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

@@ -16,6 +16,10 @@ struct FreeAPSSettings: JSON, Equatable {
     var useCalendar: Bool = false
     var glucoseBadge: Bool = false
     var glucoseNotificationsAlways: Bool = false
+    var useAlarmSound: Bool = false
+    var addSourceInfoToGlucoseNotifications: Bool = false
+    var lowGlucose: Decimal = 72
+    var highGlucose: Decimal = 270
 }
 
 extension FreeAPSSettings: Decodable {
@@ -84,6 +88,25 @@ extension FreeAPSSettings: Decodable {
             settings.glucoseNotificationsAlways = glucoseNotificationsAlways
         }
 
+        if let useAlarmSound = try? container.decode(Bool.self, forKey: .useAlarmSound) {
+            settings.useAlarmSound = useAlarmSound
+        }
+
+        if let addSourceInfoToGlucoseNotifications = try? container.decode(
+            Bool.self,
+            forKey: .addSourceInfoToGlucoseNotifications
+        ) {
+            settings.addSourceInfoToGlucoseNotifications = addSourceInfoToGlucoseNotifications
+        }
+
+        if let lowGlucose = try? container.decode(Decimal.self, forKey: .lowGlucose) {
+            settings.lowGlucose = lowGlucose
+        }
+
+        if let highGlucose = try? container.decode(Decimal.self, forKey: .highGlucose) {
+            settings.highGlucose = highGlucose
+        }
+
         self = settings
     }
 }

+ 2 - 1
FreeAPS/Sources/Modules/Base/BaseStateModel.swift

@@ -45,11 +45,12 @@ class BaseStateModel<Provider>: StateModel, Injectable where Provider: FreeAPS.P
 
     func subscribeSetting<T: Equatable, U: Publisher>(
         _ keyPath: WritableKeyPath<FreeAPSSettings, T>,
-        on settingPublisher: U, initial: (T) -> Void, didSet: ((T) -> Void)? = nil
+        on settingPublisher: U, initial: (T) -> Void, map: ((T) -> (T))? = nil, didSet: ((T) -> Void)? = nil
     ) where U.Output == T, U.Failure == Never {
         initial(settingsManager.settings[keyPath: keyPath])
         settingPublisher
             .removeDuplicates()
+            .map(map ?? { $0 })
             .sink { [weak self] value in
                 self?.settingsManager.settings[keyPath: keyPath] = value
                 didSet?(value)

+ 26 - 4
FreeAPS/Sources/Modules/NotificationsConfig/NotificationsConfigStateModel.swift

@@ -4,13 +4,35 @@ extension NotificationsConfig {
     final class StateModel: BaseStateModel<Provider> {
         @Published var glucoseBadge = false
         @Published var glucoseNotificationsAlways = false
+        @Published var useAlarmSound = false
+        @Published var addSourceInfoToGlucoseNotifications = false
+        @Published var lowGlucose: Decimal = 0
+        @Published var highGlucose: Decimal = 0
+        var units: GlucoseUnits = .mmolL
 
         override func subscribe() {
-            glucoseBadge = settingsManager.settings.glucoseBadge
-            glucoseNotificationsAlways = settingsManager.settings.glucoseNotificationsAlways
+            let units = settingsManager.settings.units
+            self.units = units
 
-            subscribeSetting(\.glucoseBadge, on: $glucoseBadge)
-            subscribeSetting(\.glucoseNotificationsAlways, on: $glucoseNotificationsAlways)
+            subscribeSetting(\.glucoseBadge, on: $glucoseBadge) { glucoseBadge = $0 }
+            subscribeSetting(\.glucoseNotificationsAlways, on: $glucoseNotificationsAlways) { glucoseNotificationsAlways = $0 }
+            subscribeSetting(\.useAlarmSound, on: $useAlarmSound) { useAlarmSound = $0 }
+            subscribeSetting(\.addSourceInfoToGlucoseNotifications, on: $addSourceInfoToGlucoseNotifications) {
+                addSourceInfoToGlucoseNotifications = $0 }
+
+            subscribeSetting(\.lowGlucose, on: $lowGlucose, initial: {
+                lowGlucose = units == .mmolL ? $0.asMmolL : $0
+            }, map: {
+                guard units == .mmolL else { return $0 }
+                return $0.asMgdL
+            })
+
+            subscribeSetting(\.highGlucose, on: $highGlucose, initial: {
+                highGlucose = units == .mmolL ? $0.asMmolL : $0
+            }, map: {
+                guard units == .mmolL else { return $0 }
+                return $0.asMgdL
+            })
         }
     }
 }

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

@@ -6,10 +6,38 @@ extension NotificationsConfig {
         let resolver: Resolver
         @StateObject var state = StateModel()
 
+        private var glucoseFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 0
+            if state.units == .mmolL {
+                formatter.maximumFractionDigits = 1
+            }
+            formatter.roundingMode = .halfUp
+            return formatter
+        }
+
         var body: some View {
             Form {
                 Section(header: Text("Glucose")) {
                     Toggle("Show glucose on the app badge", isOn: $state.glucoseBadge)
+                    Toggle("Always Notify Glucose", isOn: $state.glucoseNotificationsAlways)
+                    Toggle("Also play alert sound", isOn: $state.useAlarmSound)
+                    Toggle("Also add source info", isOn: $state.addSourceInfoToGlucoseNotifications)
+
+                    HStack {
+                        Text("Low")
+                        Spacer()
+                        DecimalTextField("0", value: $state.lowGlucose, formatter: glucoseFormatter)
+                        Text(state.units.rawValue).foregroundColor(.secondary)
+                    }
+
+                    HStack {
+                        Text("High")
+                        Spacer()
+                        DecimalTextField("0", value: $state.highGlucose, formatter: glucoseFormatter)
+                        Text(state.units.rawValue).foregroundColor(.secondary)
+                    }
                 }
             }
             .onAppear(perform: configureView)

+ 78 - 54
FreeAPS/Sources/Services/UserNotifiactions/UserNotificationsManager.swift

@@ -56,19 +56,17 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
     private func sendGlucoseNotification() {
         addAppBadge(glucose: nil)
 
-        ensureCanSendNotification {
-            let glucose = self.glucoseStorage.recent()
-            guard let lastGlucose = glucose.last, let glucoseValue = lastGlucose.glucose else { return }
+        let glucose = glucoseStorage.recent()
 
-            let delta: Int?
-            if glucose.count >= 2 {
-                delta = glucoseValue - (glucose[glucose.count - 2].glucose ?? 0)
-            } else {
-                delta = nil
-            }
+        guard let lastGlucose = glucose.last, let glucoseValue = lastGlucose.glucose else { return }
 
-            let content = UNMutableNotificationContent()
+        addAppBadge(glucose: lastGlucose.glucose)
+
+        guard glucoseStorage.alarm != nil || settingsManager.settings.glucoseNotificationsAlways else {
+            return
+        }
 
+        ensureCanSendNotification {
             var titles: [String] = []
 
             switch self.glucoseStorage.alarm {
@@ -76,61 +74,76 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
                 titles.append(NSLocalizedString("Glucose", comment: "Glucose"))
             case .low:
                 titles.append(NSLocalizedString("LOWALERT!", comment: "LOWALERT!"))
-                self.playSound()
+                self.playSoundIfNeeded()
             case .high:
                 titles.append(NSLocalizedString("HIGHALERT!", comment: "HIGHALERT!"))
-                self.playSound()
+                self.playSoundIfNeeded()
             }
 
-            let units = self.settingsManager.settings.units
-
-            let glucoseText = self.glucoseFormatter
-                .string(from: Double(
-                    units == .mmolL ? glucoseValue
-                        .asMmolL : Decimal(glucoseValue)
-                ) as NSNumber)! + " " + NSLocalizedString(units.rawValue, comment: "units")
-            let directionText = lastGlucose.direction?.symbol ?? "↔︎"
-            let deltaText = delta
-                .map {
-                    self.deltaFormatter
-                        .string(from: Double(
-                            units == .mmolL ? $0
-                                .asMmolL : Decimal($0)
-                        ) as NSNumber)!
-                } ?? "--"
-
-            var body = glucoseText + " " + directionText + " " + deltaText
+            let delta = glucose.count >= 2 ? glucoseValue - (glucose[glucose.count - 2].glucose ?? 0) : nil
+
+            let body = self.glucoseText(glucoseValue: glucoseValue, delta: delta, direction: lastGlucose.direction) + self
+                .infoBody()
+
             titles.append(body)
 
+            let content = UNMutableNotificationContent()
             content.title = titles.joined(separator: " ")
+            content.body = body
 
-            if let info = self.sourceInfoProvider.sourceInfo() {
-                //NS ping
-                if let ping = info[GlucoseSourceKey.nightscoutPing.rawValue] as? TimeInterval {
-                    body.append(
-                        "\n"
-                            + String(
-                                format: NSLocalizedString("Nightscout ping: %d ms", comment: "Nightscout ping"),
-                                Int(ping * 1000)
-                            )
-                    )
-                }
-
-                //Transmitter battery
-                if let transmitterBattery = info[GlucoseSourceKey.transmitterBattery.rawValue] as? Int {
-                    body.append(
-                        "\n"
-                            + String(format: NSLocalizedString("Transmitter: %@%%", comment: "Transmitter: %@%%"), transmitterBattery)
-                    )
-                }
-            }
+            self.addRequest(identifier: .glucocoseNotification, content: content, deleteOld: true)
+        }
+    }
 
-            content.body = body
+    private func glucoseText(glucoseValue: Int, delta: Int?, direction: BloodGlucose.Direction?) -> String {
+        let units = settingsManager.settings.units
+        let glucoseText = glucoseFormatter
+            .string(from: Double(
+                units == .mmolL ? glucoseValue
+                    .asMmolL : Decimal(glucoseValue)
+            ) as NSNumber)! + " " + NSLocalizedString(units.rawValue, comment: "units")
+        let directionText = direction?.symbol ?? "↔︎"
+        let deltaText = delta
+            .map {
+                self.deltaFormatter
+                    .string(from: Double(
+                        units == .mmolL ? $0
+                            .asMmolL : Decimal($0)
+                    ) as NSNumber)!
+            } ?? "--"
+
+        return glucoseText + " " + directionText + " " + deltaText
+    }
 
-            self.addAppBadge(glucose: lastGlucose.glucose)
+    private func infoBody() -> String {
+        var body = ""
+
+        if settingsManager.settings.addSourceInfoToGlucoseNotifications,
+           let info = sourceInfoProvider.sourceInfo()
+        {
+            // NS ping
+            if let ping = info[GlucoseSourceKey.nightscoutPing.rawValue] as? TimeInterval {
+                body.append(
+                    "\n"
+                        + String(
+                            format: NSLocalizedString("Nightscout ping: %d ms", comment: "Nightscout ping"),
+                            Int(ping * 1000)
+                        )
+                )
+            }
 
-            self.addRequest(identifier: .glucocoseNotification, content: content, deleteOld: true)
+            // Transmitter battery
+            if let transmitterBattery = info[GlucoseSourceKey.transmitterBattery.rawValue] as? Int {
+                body.append(
+                    "\n"
+                        + String(
+                            format: NSLocalizedString("Transmitter: %@%%", comment: "Transmitter: %@%%"),
+                            transmitterBattery
+                        )
+                )
+            }
         }
+        return body
     }
 
     private func requestNotificationPermissionsIfNeeded() {
@@ -184,16 +197,27 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
     }
 
+    private func playSoundIfNeeded() {
+        guard settingsManager.settings.useAlarmSound else { return }
+        playSound()
+    }
+
+    private let soundID: UInt32 = 1336
+
     private func playSound(times: Int = 3) {
         guard times > 0 else {
             return
         }
 
-        AudioServicesPlaySystemSoundWithCompletion(1336) {
+        AudioServicesPlaySystemSoundWithCompletion(soundID) {
             self.playSound(times: times - 1)
         }
     }
 
+    private func stopSound() {
+        AudioServicesDisposeSystemSoundID(soundID)
+    }
+
     private var glucoseFormatter: NumberFormatter {
         let formatter = NumberFormatter()
         formatter.numberStyle = .decimal