Sfoglia il codice sorgente

add notification toggles for message subtypes; prettify SwiftMessages

kskandis 1 anno fa
parent
commit
8f3d208425

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

@@ -33,6 +33,10 @@ struct FreeAPSSettings: JSON, Equatable {
     var displayCalendarIOBandCOB: Bool = false
     var displayCalendarIOBandCOB: Bool = false
     var displayCalendarEmojis: Bool = false
     var displayCalendarEmojis: Bool = false
     var glucoseBadge: Bool = false
     var glucoseBadge: Bool = false
+    var notificationsPump: Bool = false
+    var notificationsCgm: Bool = false
+    var notificationsCarb: Bool = false
+    var notificationsAlgorithm: Bool = false
     var glucoseNotificationsAlways: Bool = false
     var glucoseNotificationsAlways: Bool = false
     var useAlarmSound: Bool = false
     var useAlarmSound: Bool = false
     var addSourceInfoToGlucoseNotifications: Bool = false
     var addSourceInfoToGlucoseNotifications: Bool = false
@@ -203,6 +207,22 @@ extension FreeAPSSettings: Decodable {
             settings.delay = delay
             settings.delay = delay
         }
         }
 
 
+        if let notificationsPump = try? container.decode(Bool.self, forKey: .notificationsPump) {
+            settings.notificationsPump = notificationsPump
+        }
+
+        if let notificationsCgm = try? container.decode(Bool.self, forKey: .notificationsCgm) {
+            settings.notificationsCgm = notificationsCgm
+        }
+
+        if let notificationsCarb = try? container.decode(Bool.self, forKey: .notificationsCarb) {
+            settings.notificationsCarb = notificationsCarb
+        }
+
+        if let notificationsAlgorithm = try? container.decode(Bool.self, forKey: .notificationsAlgorithm) {
+            settings.notificationsAlgorithm = notificationsAlgorithm
+        }
+
         if let glucoseNotificationsAlways = try? container.decode(Bool.self, forKey: .glucoseNotificationsAlways) {
         if let glucoseNotificationsAlways = try? container.decode(Bool.self, forKey: .glucoseNotificationsAlways) {
             settings.glucoseNotificationsAlways = glucoseNotificationsAlways
             settings.glucoseNotificationsAlways = glucoseNotificationsAlways
         }
         }

+ 10 - 0
FreeAPS/Sources/Modules/GlucoseNotificationSettings/GlucoseNotificationSettingsStateModel.swift

@@ -9,12 +9,22 @@ extension GlucoseNotificationSettings {
         @Published var lowGlucose: Decimal = 0
         @Published var lowGlucose: Decimal = 0
         @Published var highGlucose: Decimal = 0
         @Published var highGlucose: Decimal = 0
 
 
+        @Published var notificationsPump = false
+        @Published var notificationsCgm = false
+        @Published var notificationsCarb = false
+        @Published var notificationsAlgorithm = false
+
         var units: GlucoseUnits = .mgdL
         var units: GlucoseUnits = .mgdL
 
 
         override func subscribe() {
         override func subscribe() {
             let units = settingsManager.settings.units
             let units = settingsManager.settings.units
             self.units = units
             self.units = units
 
 
+            subscribeSetting(\.notificationsPump, on: $notificationsPump) { notificationsPump = $0 }
+            subscribeSetting(\.notificationsCgm, on: $notificationsCgm) { notificationsCgm = $0 }
+            subscribeSetting(\.notificationsCarb, on: $notificationsCarb) { notificationsCarb = $0 }
+            subscribeSetting(\.notificationsAlgorithm, on: $notificationsAlgorithm) { notificationsAlgorithm = $0 }
+
             subscribeSetting(\.glucoseBadge, on: $glucoseBadge) { glucoseBadge = $0 }
             subscribeSetting(\.glucoseBadge, on: $glucoseBadge) { glucoseBadge = $0 }
             subscribeSetting(\.glucoseNotificationsAlways, on: $glucoseNotificationsAlways) { glucoseNotificationsAlways = $0 }
             subscribeSetting(\.glucoseNotificationsAlways, on: $glucoseNotificationsAlways) { glucoseNotificationsAlways = $0 }
             subscribeSetting(\.useAlarmSound, on: $useAlarmSound) { useAlarmSound = $0 }
             subscribeSetting(\.useAlarmSound, on: $useAlarmSound) { useAlarmSound = $0 }

+ 170 - 97
FreeAPS/Sources/Modules/GlucoseNotificationSettings/View/GlucoseNotificationSettingsRootView.swift

@@ -55,26 +55,94 @@ extension GlucoseNotificationSettings {
 
 
         var body: some View {
         var body: some View {
             Form {
             Form {
-                if !notificationsDisabled {
-                    SettingInputSection(
-                        decimalValue: $decimalPlaceholder,
-                        booleanValue: $state.glucoseBadge,
-                        shouldDisplayHint: $shouldDisplayHint,
-                        selectedVerboseHint: Binding(
-                            get: { selectedVerboseHint },
-                            set: {
-                                selectedVerboseHint = $0
-                                hintLabel = "Show Glucose App Badge"
-                            }
-                        ),
-                        units: state.units,
-                        type: .boolean,
-                        label: "Show Glucose App Badge",
-                        miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                        verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                        headerText: "Various Glucose Notifications"
-                    )
-                }
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.notificationsPump,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0
+                            hintLabel = "Always Notify Pump"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Always Notify Pump",
+                    miniHint: "Always Notify Pump Warnings",
+                    verboseHint: "With iOS Trio Notifications enabled, you can let Trio display most Pump Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue.\n\nIf iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.\n\nAn example of a Pump Warning is 'Pod Expiration Reminder'",
+                    headerText: "Trio Information Notifications"
+                )
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.notificationsCgm,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0
+                            hintLabel = "Always Notify CGM"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Always Notify CGM",
+                    miniHint: "Always Notify CGM Warnings",
+                    verboseHint: "With iOS Trio Notifications enabled, you can let Trio display most CGM Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue.\n\nIf iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.\n\nAn example of a CGM Warning is 'Unable to open the app'"
+                )
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.notificationsCarb,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0
+                            hintLabel = "Always Notify Carb"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Always Notify Carb",
+                    miniHint: "Always Notify Carb Warnings",
+                    verboseHint: "With iOS Trio Notifications enabled, you can let Trio display most Carb Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue.\n\nIf iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.\n\nAn example of a Carb Warning is 'Carbs required: 30 g'"
+                )
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.notificationsAlgorithm,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0
+                            hintLabel = "Always Notify Algorithm"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Always Notify Algorithm",
+                    miniHint: "Always Notify Algorithm Warnings",
+                    verboseHint: "With iOS Trio Notifications enabled, you can let Trio display most Algorithm Notifications in iOS Notification Center as a Banner, List and on the Lock Screen. It allows you to refer to Trio Information at a glance and troubleshoot any informational issue.\n\nIf iOS Trio Notifications is disabled, Trio will display these messages in-app as a banner only.\n\nAn example of a Algorithm Warning is 'Error: Invalid glucose: Not enough glucose data'"
+                )
+
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.glucoseBadge,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0
+                            hintLabel = "Show Glucose App Badge"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Show Glucose App Badge",
+                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
+                    verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
+                    headerText: "Various Glucose Notifications"
+                )
 
 
                 SettingInputSection(
                 SettingInputSection(
                     decimalValue: $decimalPlaceholder,
                     decimalValue: $decimalPlaceholder,
@@ -84,7 +152,7 @@ extension GlucoseNotificationSettings {
                         get: { selectedVerboseHint },
                         get: { selectedVerboseHint },
                         set: {
                         set: {
                             selectedVerboseHint = $0
                             selectedVerboseHint = $0
-                            hintLabel = "Always Notify Glucose"
+                            hintLabel = "e"
                         }
                         }
                     ),
                     ),
                     units: state.units,
                     units: state.units,
@@ -94,82 +162,81 @@ extension GlucoseNotificationSettings {
                     verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
                     verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
                 )
                 )
 
 
-                if !notificationsDisabled {
-                    SettingInputSection(
-                        decimalValue: $decimalPlaceholder,
-                        booleanValue: $state.useAlarmSound,
-                        shouldDisplayHint: $shouldDisplayHint,
-                        selectedVerboseHint: Binding(
-                            get: { selectedVerboseHint },
-                            set: {
-                                selectedVerboseHint = $0
-                                hintLabel = "Play Alarm Sound"
-                            }
-                        ),
-                        units: state.units,
-                        type: .boolean,
-                        label: "Play Alarm Sound",
-                        miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                        verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
-                    )
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.useAlarmSound,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0
+                            hintLabel = "Play Alarm Sound"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Play Alarm Sound",
+                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
+                    verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                )
 
 
-                    SettingInputSection(
-                        decimalValue: $decimalPlaceholder,
-                        booleanValue: $state.addSourceInfoToGlucoseNotifications,
-                        shouldDisplayHint: $shouldDisplayHint,
-                        selectedVerboseHint: Binding(
-                            get: { selectedVerboseHint },
-                            set: {
-                                selectedVerboseHint = $0
-                                hintLabel = "Add Glucose Source to Alarm"
-                            }
-                        ),
-                        units: state.units,
-                        type: .boolean,
-                        label: "Add Glucose Source to Alarm",
-                        miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
-                        verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
-                    )
+                SettingInputSection(
+                    decimalValue: $decimalPlaceholder,
+                    booleanValue: $state.addSourceInfoToGlucoseNotifications,
+                    shouldDisplayHint: $shouldDisplayHint,
+                    selectedVerboseHint: Binding(
+                        get: { selectedVerboseHint },
+                        set: {
+                            selectedVerboseHint = $0
+                            hintLabel = "Add Glucose Source to Alarm"
+                        }
+                    ),
+                    units: state.units,
+                    type: .boolean,
+                    label: "Add Glucose Source to Alarm",
+                    miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
+                    verboseHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
+                )
 
 
-                    Section {
-                        HStack {
-                            Text("Low Glucose Alarm Limit")
-                            Spacer()
-                            TextFieldWithToolBar(text: $state.lowGlucose, placeholder: "0", numberFormatter: glucoseFormatter)
-                            Text(state.units.rawValue).foregroundColor(.secondary)
-                        }.padding(.top)
+                Section {
+                    HStack {
+                        Text("Low Glucose Alarm Limit")
+                        Spacer()
+                        TextFieldWithToolBar(text: $state.lowGlucose, placeholder: "0", numberFormatter: glucoseFormatter)
+                        Text(state.units.rawValue).foregroundColor(.secondary)
+                    }.padding(.top)
 
 
-                        HStack {
-                            Text("High Glucose Alarm Limit")
-                            Spacer()
-                            TextFieldWithToolBar(text: $state.highGlucose, placeholder: "0", numberFormatter: glucoseFormatter)
-                            Text(state.units.rawValue).foregroundColor(.secondary)
-                        }
+                    HStack {
+                        Text("High Glucose Alarm Limit")
+                        Spacer()
+                        TextFieldWithToolBar(text: $state.highGlucose, placeholder: "0", numberFormatter: glucoseFormatter)
+                        Text(state.units.rawValue).foregroundColor(.secondary)
+                    }
 
 
-                        HStack(alignment: .top) {
-                            Text(
-                                "Set the lower and upper limit for glucose alarms. See hint for more details."
-                            )
-                            .font(.footnote)
-                            .foregroundColor(.secondary)
-                            .lineLimit(nil)
-                            Spacer()
-                            Button(
-                                action: {
-                                    hintLabel = "Low and High Glucose Alarm Limits"
-                                    selectedVerboseHint =
-                                        "These two settings limit the range outside of which you will be notified via push notifications. If your CGM readings are below 'Low' or above 'High', you will receive a glucose alarm."
-                                    shouldDisplayHint.toggle()
-                                },
-                                label: {
-                                    HStack {
-                                        Image(systemName: "questionmark.circle")
-                                    }
+                    HStack(alignment: .top) {
+                        Text(
+                            "Set the lower and upper limit for glucose alarms. See hint for more details."
+                        )
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+                        .lineLimit(nil)
+                        Spacer()
+                        Button(
+                            action: {
+                                hintLabel = "Low and High Glucose Alarm Limits"
+                                selectedVerboseHint =
+                                    "These two settings limit the range outside of which you will be notified via push notifications. If your CGM readings are below 'Low' or above 'High', you will receive a glucose alarm."
+                                shouldDisplayHint.toggle()
+                            },
+                            label: {
+                                HStack {
+                                    Image(systemName: "questionmark.circle")
                                 }
                                 }
-                            ).buttonStyle(BorderlessButtonStyle())
-                        }.padding(.vertical)
-                    }
-                    .listRowBackground(Color.chart)
+                            }
+                        ).buttonStyle(BorderlessButtonStyle())
+                    }.padding(.vertical)
+//                    }
+                        .listRowBackground(Color.chart)
                 }
                 }
             }
             }
             .sheet(isPresented: $shouldDisplayHint) {
             .sheet(isPresented: $shouldDisplayHint) {
@@ -181,12 +248,18 @@ extension GlucoseNotificationSettings {
                     sheetTitle: "Help"
                     sheetTitle: "Help"
                 )
                 )
             }
             }
-            .onReceive(resolver.resolve(AlertPermissionsChecker.self)!.$notificationsDisabled, perform: {
-                notificationsDisabled = $0
-            })
+            .onReceive(
+                resolver.resolve(AlertPermissionsChecker.self)!.$notificationsDisabled,
+                perform: {
+                    notificationsDisabled = $0
+                }
+            )
             .scrollContentBackground(.hidden).background(color)
             .scrollContentBackground(.hidden).background(color)
-            .onAppear(perform: configureView)
-            .navigationBarTitle("Glucose Notifications")
+//            .onAppear(perform: configureView)
+            .onAppear {
+                configureView {}
+            }
+            .navigationBarTitle("Trio Notifications")
             .navigationBarTitleDisplayMode(.automatic)
             .navigationBarTitleDisplayMode(.automatic)
         }
         }
     }
     }

+ 265 - 51
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -17,6 +17,7 @@ extension Home {
         @State private var statusTitle: String = ""
         @State private var statusTitle: String = ""
         @State var showPumpSelection: Bool = false
         @State var showPumpSelection: Bool = false
         @State var notificationsDisabled = false
         @State var notificationsDisabled = false
+        @State var alertSafetyNotificationsViewHeight = 0
 
 
         struct Buttons: Identifiable {
         struct Buttons: Identifiable {
             let label: String
             let label: String
@@ -57,6 +58,158 @@ extension Home {
 
 
         // TODO: end todo
         // TODO: end todo
 
 
+        func sendTestRepeat(storedMessages: [MessageContent], repeats: Bool = false) { // TODO: REMOVE!!!
+            if repeats == true {
+                for loop in 0 ... 5 {
+                    for i in 0 ... storedMessages.count - 1 {
+                        var count = 0
+                        _ = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { t in
+                            print(count)
+                            print(storedMessages[count].content)
+                            router.alertMessage.send(storedMessages[count])
+                            count += 1
+                            if count >= storedMessages.count {
+                                t.invalidate()
+                            }
+                        }
+                    }
+                }
+            } else {
+                for i in 0 ... storedMessages.count - 1 {
+                    print(i)
+                    print(storedMessages[i].content)
+                    router.alertMessage.send(storedMessages[i])
+                }
+            }
+        }
+
+        func sendTestTriggerMessage() { // TODO: REMOVE!!!
+            var storedMessages: [MessageContent] = []
+            var messageCont: MessageContent
+
+            let firstInterval = 1 // min
+            let secondInterval = 2 // min
+            let firstTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 60 * TimeInterval(firstInterval), repeats: false)
+            messageCont = MessageContent(
+                content: "Last Loop was more than 20 min ago - TEST",
+                type: MessageType.info,
+                subtype: .algorithm,
+                title: "Trio Not Active",
+                useAPN: false,
+                trigger: firstTrigger
+            )
+            debug(
+                .default,
+                "TEST \(messageCont.title) \(messageCont.content) \(messageCont.type) \(messageCont.subtype)"
+            )
+            storedMessages.append(messageCont)
+
+            let secondTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 60 * TimeInterval(secondInterval), repeats: false)
+            messageCont = MessageContent(
+                content: "Last Loop was more than 40 min ago - TEST",
+                type: MessageType.warning,
+                subtype: .algorithm,
+                title: "Trio Not Active",
+                useAPN: false,
+                trigger: secondTrigger
+            )
+            debug(
+                .default,
+                "TEST \(messageCont.title) \(messageCont.content) \(messageCont.type) \(messageCont.subtype)"
+            )
+            storedMessages.append(messageCont)
+
+            sendTestRepeat(storedMessages: storedMessages, repeats: true)
+        }
+
+        func sendTestNotifications() { // TODO: REMOVE!!!
+            var storedMessages: [MessageContent] = []
+            var messageCont: MessageContent
+
+            sendTestTriggerMessage()
+//            return
+            messageCont = MessageContent(
+                // content: "68 mg/dL" + "↔︎" + "-1" + "\n" + "Plugin CGM Source",
+                content: "68 mg/dL" + "↔︎" + "-1",
+                type: MessageType.warning,
+                subtype: .glucose,
+                title: "LOWALERT! 68 mg/dL" + "↔︎" + "-1",
+                useAPN: true
+            )
+            router.alertMessage.send(messageCont)
+
+            messageCont = MessageContent(
+                content: "Insulin delivery stopped. Change Pod now.",
+                type: MessageType.error, // errorPump
+                subtype: .pump,
+                title: "Critical Pod Fault 008",
+                action: .pumpConfig
+            )
+            storedMessages.append(messageCont)
+
+            messageCont = MessageContent(
+                content: "Pod expires in 68 hours.",
+                type: MessageType.warning,
+                subtype: .pump,
+                title: "Pod Expiration Reminder"
+            )
+            storedMessages.append(messageCont)
+            messageCont = MessageContent(
+                content: "10 U insulin or less remaining in Pod. Change Pod soon.",
+                type: MessageType.warning,
+                subtype: .pump,
+                title: "Low Reservoir"
+            )
+            storedMessages.append(messageCont)
+
+            messageCont = MessageContent(
+                content: "To prevent LOW required 30 g of carbs",
+                type: MessageType.warning,
+                subtype: .carb,
+                title: "Carbs required: 30 g"
+            )
+            storedMessages.append(messageCont)
+
+            messageCont = MessageContent(
+                content: "83 mg/dL" + "↔︎" + "-1" + "\n" + "Plugin CGM Source",
+                type: MessageType.other,
+                subtype: .glucose,
+                title: "Glucose 83 mg/dL" + "↔︎" + "-1"
+            )
+            storedMessages.append(messageCont)
+            messageCont = MessageContent(
+                content: "68 mg/dL" + "↔︎" + "-1" + "\n" + "Plugin CGM Source",
+                type: MessageType.warning,
+                subtype: .glucose,
+                title: "LOWALERT! 68 mg/dL" + "↔︎" + "-1"
+            )
+            storedMessages.append(messageCont)
+
+            messageCont = MessageContent(
+                content: "Error: Invalid glucose: Not enough glucose data",
+                type: MessageType.info,
+                subtype: .algorithm
+            )
+            storedMessages.append(messageCont)
+
+//            info(.apsManager, "Not enough glucose data")
+//            info(.apsManager, "Glucose data is stale")
+//            info(.apsManager, "Glucose data is too flat")
+//            info(.apsManager, "Glucose validation failed")
+//            info(.apsManager, "Loop not possible during the manual basal temp")
+//            info(.apsManager, "Temp Basal failed with error")
+//            info(.apsManager, "Pump not suspended by Announcement")
+
+//            sendTestRepeat(storedMessages: storedMessages, repeats: true)
+
+            sendTestRepeat(storedMessages: storedMessages, repeats: false)
+            for i in 0 ... storedMessages.count - 1 {
+                print(i)
+                print(storedMessages[i].content)
+                router.alertMessage.send(storedMessages[i])
+            }
+        }
+
         var bolusProgressFormatter: NumberFormatter {
         var bolusProgressFormatter: NumberFormatter {
             let formatter = NumberFormatter()
             let formatter = NumberFormatter()
             formatter.numberStyle = .decimal
             formatter.numberStyle = .decimal
@@ -129,22 +282,6 @@ extension Home {
             }
             }
         }
         }
 
 
-        private func sendSafetyNotification() {
-            let messageCont = MessageContent(
-                content: NSLocalizedString(
-                    "Fix now by turning Notifications ON.",
-                    comment: "Secondary text for alerts disabled warning, which appears on the main status screen."
-                ),
-                type: MessageType.alertPermissionWarning,
-                title: NSLocalizedString(
-                    "⚠️ Safety Notifications are OFF", // \u{26A0}
-                    comment: "Warning text for when Notifications or Critical Alerts Permissions is disabled"
-                ),
-                useAPN: false
-            )
-            router.alertMessage.send(messageCont)
-        }
-
         var glucoseView: some View {
         var glucoseView: some View {
             CurrentGlucoseView(
             CurrentGlucoseView(
                 timerDate: $state.timerDate,
                 timerDate: $state.timerDate,
@@ -698,41 +835,122 @@ extension Home {
             }
             }
         }
         }
 
 
-        @ViewBuilder func mainView() -> some View {
+        @ViewBuilder func alertSafetyNotificationsView(geo: GeometryProxy) -> some View {
+            ZStack {
+                /// rectangle as background
+                RoundedRectangle(cornerRadius: 15)
+                    .fill(
+                        Color(
+                            red: 0.9,
+                            green: 0.133333333,
+                            blue: 0.2156862745
+                        )
+                    )
+                    .clipShape(RoundedRectangle(cornerRadius: 15))
+                    .frame(height: geo.size.height * 0.08)
+                    .coordinateSpace(name: "alertSafetyNotificationsView")
+                    .shadow(
+                        color: colorScheme == .dark ? Color(red: 0.02745098039, green: 0.1098039216, blue: 0.1411764706) :
+                            Color.black.opacity(0.33),
+                        radius: 3
+                    )
+                HStack {
+                    Spacer()
+                    VStack {
+                        Text("⚠️ Safety Notifications are OFF")
+                            .font(.subheadline)
+                            .font(.system(size: 15, weight: .bold, design: .rounded))
+                            .foregroundStyle(.white.gradient)
+                            .frame(maxWidth: .infinity, alignment: .leading)
+                        Text("Fix now by turning Notifications ON.")
+                            .font(.caption)
+                            .font(.system(size: 12, weight: .bold, design: .rounded))
+                            .foregroundStyle(.white.gradient)
+                            .frame(maxWidth: .infinity, alignment: .leading)
+                    }.padding(.leading, 5)
+                    Spacer()
+                    Image(systemName: "chevron.right").foregroundColor(.white)
+                        .font(.system(size: 15, design: .rounded))
+                }.padding(.horizontal, 10)
+                    .padding(.trailing, 8)
+                    .onTapGesture {
+                        UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
+                    }
+            }.padding(.horizontal, 10)
+                .padding(.top, 0)
+        }
+
+        @ViewBuilder func mainViewWithScrollView() -> some View {
             GeometryReader { geo in
             GeometryReader { geo in
-                VStack(spacing: 0) {
-                    ZStack {
-                        /// glucose bobble
-                        glucoseView
+                ScrollView(.vertical, showsIndicators: false) {
+                    mainViewViews(geo)
+                }
+            }
+        }
 
 
-                        /// right panel with loop status and evBG
-                        HStack {
-                            Spacer()
-                            rightHeaderPanel(geo)
-                        }.padding(.trailing, 20)
+        @ViewBuilder func mainViewViews(_ geo: GeometryProxy) -> some View {
+            VStack(spacing: 0) {
+                if notificationsDisabled {
+                    alertSafetyNotificationsView(geo: geo)
+                        .padding(.top, UIDevice.adjustPadding(min: nil, max: 40))
+                }
+                ZStack(alignment: .top) {
+                    /// glucose bobble
+                    glucoseView
 
 
-                        /// left panel with pump related info
-                        HStack {
-                            pumpView
-                            Spacer()
-                        }.padding(.leading, 20)
-                    }.padding(.top, 10)
+                    /// right panel with loop status and evBG
+                    HStack {
+                        Spacer()
+                        rightHeaderPanel(geo)
+                    }.padding(.trailing, 20)
 
 
-                    mealPanel(geo).padding(.top, UIDevice.adjustPadding(min: nil, max: 30))
-                        .padding(.bottom, UIDevice.adjustPadding(min: nil, max: 20))
+                    /// left panel with pump related info
+                    HStack {
+                        pumpView
+                        Spacer()
+                    }.padding(.leading, 20)
+                }.padding(.top, 10)
 
 
-                    mainChart(geo: geo)
+                mealPanel(geo).padding(.top, UIDevice.adjustPadding(min: nil, max: 30))
+                    .padding(.bottom, UIDevice.adjustPadding(min: nil, max: 20))
 
 
-                    timeInterval.padding(.top, UIDevice.adjustPadding(min: 0, max: 12))
-                        .padding(.bottom, UIDevice.adjustPadding(min: 0, max: 12))
+                mainChart(geo: geo)
 
 
-                    if let progress = state.bolusProgress {
-                        bolusView(geo: geo, progress).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 40))
-                    } else {
-                        profileView(geo: geo).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 40))
+                timeInterval.padding(.top, UIDevice.adjustPadding(min: 0, max: 12))
+                    .padding(.bottom, UIDevice.adjustPadding(min: 0, max: 12))
+
+                if let progress = state.bolusProgress {
+                    bolusView(geo: geo, progress)
+                        .padding(.bottom, UIDevice.adjustPadding(min: nil, max: 40))
+                } else {
+                    profileView(geo: geo).padding(.bottom, UIDevice.adjustPadding(min: nil, max: 40))
+                }
+            }
+            .background(color)
+            .onReceive(
+                resolver.resolve(AlertPermissionsChecker.self)!.$notificationsDisabled,
+                perform: { // AlertPermissionsChecker
+                    if notificationsDisabled != $0 {
+                        notificationsDisabled = $0
+                        if notificationsDisabled {
+                            debug(.default, "notificationsDisabled")
+                        }
+                    }
+                }
+            )
+        }
+
+        @ViewBuilder func mainView() -> some View {
+            GeometryReader { geo in
+                if notificationsDisabled {
+                    ScrollView(.vertical, showsIndicators: false) {
+                        mainViewViews(geo)
+                    }
+                } else {
+                    GeometryReader { geo in
+                        mainViewViews(geo)
                     }
                     }
                 }
                 }
-                .background(color)
             }
             }
             .onChange(of: state.hours) { _ in
             .onChange(of: state.hours) { _ in
                 highlightButtons()
                 highlightButtons()
@@ -742,14 +960,6 @@ extension Home {
                     highlightButtons()
                     highlightButtons()
                 }
                 }
             }
             }
-            .onReceive(resolver.resolve(AlertPermissionsChecker.self)!.$notificationsDisabled, perform: {
-                if notificationsDisabled != $0 {
-                    notificationsDisabled = $0
-                    if notificationsDisabled {
-                        sendSafetyNotification()
-                    }
-                }
-            })
             .navigationTitle("Home")
             .navigationTitle("Home")
             .navigationBarHidden(true)
             .navigationBarHidden(true)
             .ignoresSafeArea(.keyboard)
             .ignoresSafeArea(.keyboard)
@@ -764,6 +974,7 @@ extension Home {
                     )
                     )
                     .onTapGesture {
                     .onTapGesture {
                         state.isStatusPopupPresented = false
                         state.isStatusPopupPresented = false
+                        sendTestNotifications() // TODO: Remove!
                     }
                     }
                     .gesture(
                     .gesture(
                         DragGesture(minimumDistance: 10, coordinateSpace: .local)
                         DragGesture(minimumDistance: 10, coordinateSpace: .local)
@@ -1028,7 +1239,10 @@ extension UIDevice {
         case largeDevice = 852 // Height for 6.1" iPhone 15 Pro
         case largeDevice = 852 // Height for 6.1" iPhone 15 Pro
     }
     }
 
 
-    @usableFromInline static func adjustPadding(min: CGFloat? = nil, max: CGFloat? = nil) -> CGFloat? {
+    @usableFromInline static func adjustPadding(
+        min: CGFloat? = nil,
+        max: CGFloat? = nil
+    ) -> CGFloat? {
         if UIScreen.screenHeight > UIDevice.DeviceSize.smallDevice.rawValue {
         if UIScreen.screenHeight > UIDevice.DeviceSize.smallDevice.rawValue {
             if UIScreen.screenHeight >= UIDevice.DeviceSize.largeDevice.rawValue {
             if UIScreen.screenHeight >= UIDevice.DeviceSize.largeDevice.rawValue {
                 return max
                 return max

+ 247 - 144
FreeAPS/Sources/Modules/Main/MainStateModel.swift

@@ -13,86 +13,160 @@ extension Main {
         @Published var isSecondaryModalPresented = false
         @Published var isSecondaryModalPresented = false
         @Published var secondaryModalView: AnyView? = nil
         @Published var secondaryModalView: AnyView? = nil
 
 
-        private var storedMessages: [MessageContent] = []
-        private let maxStoredMessages = 3
-        private let maxNotificationsPerMinute = 3
-        private var lastMessageTimestamp: Date?
-        private var timer: AnyCancellable?
-        private var timeInterval: TimeInterval = 1
-        private let limitInterval: TimeInterval = 20
-        private var lastNotificationTime: TimeInterval = 0
-        private var sentNotifications: [TimeInterval] = []
-
-        // Method to queue new message and check if it matches the "NOTE-*" pattern
-        func queueMessageIfNeeded(_ message: MessageContent) {
-            if message.type != MessageType.info {
-                showAlertMessage(message)
-                return
+        @Persisted(key: "UserNotificationsManager.snoozeUntilDate") private var snoozeUntilDate: Date = .distantPast
+        private var timers: [TimeInterval: Timer] = [:]
+
+        private var formatter: DateComponentsFormatter { // TODO: Remove debug only
+            let formatter = DateComponentsFormatter()
+            formatter.allowsFractionalUnits = false
+            formatter.unitsStyle = .full
+            return formatter
+        }
+
+        private var dateFormatter: DateFormatter { // TODO: Remove debug only
+            let formatter = DateFormatter()
+            formatter.timeStyle = .short
+            return formatter
+        }
+
+        private func formatInterval(_ interval: TimeInterval) -> String { // TODO: Remove debug only
+            formatter.string(from: interval)!
+        }
+
+        private func showTriggeredView(
+            message: MessageContent,
+            interval: TimeInterval,
+            config: SwiftMessages.Config,
+            view: MessageView
+        ) {
+            let snoozeFor = formatter.string(from: interval)! // TODO: Remove debug only
+            let untilDate = Date() + interval
+            debug(
+                .default,
+                "Notification triggered for: \n \(String(describing: view.titleLabel?.text)) \(String(describing: view.bodyLabel?.text)) snoozed for \(snoozeFor) until \(dateFormatter.string(from: untilDate))"
+            )
+
+            view.customConfigureTheme(
+                colorSchemePreference: colorSchemePreference
+            )
+            setupAction(message: message, view: view)
+
+            SwiftMessages.show(config: config, view: view)
+        }
+
+        // Add or replace timer for a specific TimeInterval
+        private func addOrReplaceTriggerTimer(message: MessageContent, config: SwiftMessages.Config, view: MessageView) {
+            let trigger = message.trigger as! UNTimeIntervalNotificationTrigger
+            guard trigger.timeInterval > 0 else { return }
+            let interval = trigger.timeInterval
+
+//            let interval = message.content.contains("20") ? TimeInterval(60) :
+//                TimeInterval(120) // TimeInterval(60) // trigger.timeInterval // TODO: remove 60 secs for test
+            let snoozeFor = formatter.string(from: interval)! // TODO: Remove debug only
+            let untilDate = Date() + interval
+            debug(
+                .default,
+                "\(message.title) \(message.content) \(message.type) \(message.subtype) will snooze for \(snoozeFor) until \(dateFormatter.string(from: untilDate)) for view.id \(view.id)"
+            )
+
+            SwiftMessages.hide(id: view.id)
+
+            // If a timer already exists for this interval, invalidate it
+            if let existingTimer = timers[interval] {
+                existingTimer.invalidate()
             }
             }
-            if !storedMessages.filter({ $0.content == message.content && $0.title == message.title }).isEmpty { return }
 
 
-            storedMessages.append(message)
-            lastMessageTimestamp = Date()
+            // Create a new timer with the provided interval
+            let newTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in
+                self?.showTriggeredView(message: message, interval: interval, config: config, view: view)
+                self?.timers[interval] = nil
+            }
 
 
-            // If we have accumulated messages, concatenate and display
-            if storedMessages.count >= maxStoredMessages {
-                checkAndDisplayStoredMessages()
-            } else {
-                startTimer()
+            timers[interval] = newTimer
+        }
+
+        // Cancel all timers (optional cleanup method)
+        private func cancelAllTimers() {
+            timers.values.forEach { $0.invalidate() }
+            timers.removeAll()
+        }
+
+        private func setupPumpConfig() {
+            // display the pump configuration immediatly
+            if let pump = provider.deviceManager.pumpManager,
+               let bluetooth = provider.bluetoothProvider
+            {
+                let view = PumpConfig.PumpSettingsView(
+                    pumpManager: pump,
+                    bluetoothManager: bluetooth,
+                    completionDelegate: self
+                ).asAny()
+                router.mainSecondaryModalView.send(view)
             }
             }
         }
         }
 
 
-        // Start or restart the timer that checks for the 1-minute interval
-        private func startTimer() {
-            timer = Timer.publish(every: timeInterval, on: .main, in: .common)
-                .autoconnect()
-                .sink { [weak self] _ in
-                    self?.checkAndDisplayStoredMessages()
-                }
+        private func setupButton(message _: MessageContent, view: MessageView) {
+            view.button?.setImage(UIImage(), for: .normal)
+            view.iconLabel = nil
+            let buttonImage = UIImage(systemName: "chevron.right")?.withTintColor(.white)
+            view.button?.setImage(buttonImage, for: .normal)
+            view.button?.backgroundColor = view.backgroundView.backgroundColor
+            view.button?.tintColor = view.iconImageView?.tintColor // .foregroundColor
         }
         }
 
 
-        // Method to check the stored messages and show them after 1 minute
-        private func checkAndDisplayStoredMessages() {
-            guard !storedMessages.isEmpty else { return }
-
-            // Ensure rate limit is not exceeded
-            let currentTime = Date().timeIntervalSince1970
-            pruneOldNotifications(currentTime: currentTime)
-
-            // Ensure we do not exceed maxNotificationsPerMinute
-            if sentNotifications.count < maxNotificationsPerMinute {
-                // If below the limit, send the next notification in the queue
-                if !alertPermissionsChecker.notificationsDisabled {
-                    let request = storedMessages.removeFirst()
-                    showAlertMessage(request)
-                    sentNotifications.append(currentTime)
-                } else {
-                    let max = storedMessages.count >= maxStoredMessages ? maxStoredMessages : storedMessages.count
-                    var content = ""
-                    for _ in 1 ... max {
-                        let request = storedMessages.removeFirst()
-                        sentNotifications.append(currentTime)
-                        content = content + request.content + "\n"
-                    }
-                    if content != "" {
-                        let messageCont = MessageContent(
-                            content: content,
-                            type: MessageType.other
-                        )
-                        showAlertMessage(messageCont)
-                    }
+        private func setupAction(message: MessageContent, view: MessageView) {
+            switch message.action {
+            case .snooze:
+                setupButton(message: message, view: view)
+                view.buttonTapHandler = { _ in
+                    // Popup Snooze view when user taps on Glucose Notification
+                    SwiftMessages.hide()
+                    self.router.mainModalScreen.send(.snooze)
+                }
+            case .pumpConfig:
+                setupButton(message: message, view: view)
+                view.buttonTapHandler = { _ in
+                    SwiftMessages.hide()
+                    self.setupPumpConfig()
+                }
+            default: // break
+                view.button?.setImage(UIImage(), for: .normal)
+                view.buttonTapHandler = { _ in
+                    SwiftMessages.hide()
                 }
                 }
             }
             }
         }
         }
 
 
-        // Remove notifications from the sent list that are older than `limitInterval`
-        private func pruneOldNotifications(currentTime: TimeInterval) {
-            // Remove any notifications older than `limitInterval`
-            sentNotifications = sentNotifications.filter { currentTime - $0 < limitInterval }
+        private func isApnPumpConfigAction(_ message: MessageContent) -> Bool {
+            if message.type != .error, message.action == .pumpConfig {
+                setupPumpConfig()
+                return true
+            }
+            return false
+        }
+
+        private func allowNotify(_ message: MessageContent) -> Bool {
+            if message.type == .error { return true } // .errorPump
+            switch message.subtype {
+            case .pump:
+                guard settingsManager.settings.notificationsPump else { return false }
+            case .cgm:
+                guard settingsManager.settings.notificationsCgm else { return false }
+            case .carb:
+                guard settingsManager.settings.notificationsCarb else { return false }
+            case .glucose:
+                guard settingsManager.settings.glucoseNotificationsAlways else { return false }
+            case .algorithm:
+                guard settingsManager.settings.notificationsAlgorithm else { return false }
+            case .misc:
+                return true
+            }
+            return true
         }
         }
 
 
         private func showAlertMessage(_ message: MessageContent) {
         private func showAlertMessage(_ message: MessageContent) {
-            if message.useAPN, !alertPermissionsChecker.notificationsDisabled, message.type != MessageType.pumpConfig {
+            if message.useAPN, !alertPermissionsChecker.notificationsDisabled
+            {
                 showAPN(message)
                 showAPN(message)
             } else {
             } else {
                 showSwiftMessage(message)
                 showSwiftMessage(message)
@@ -100,35 +174,40 @@ extension Main {
         }
         }
 
 
         private func showAPN(_ message: MessageContent) {
         private func showAPN(_ message: MessageContent) {
-            let messageCont = MessageContent(content: message.content, type: message.type)
-            switch message.type {
-            case .pumpConfig:
-                if let pump = provider.deviceManager.pumpManager,
-                   let bluetooth = provider.bluetoothProvider
-                {
-                    let view = PumpConfig.PumpSettingsView(
-                        pumpManager: pump,
-                        bluetoothManager: bluetooth,
-                        completionDelegate: self
-                    ).asAny()
-                    router.mainSecondaryModalView.send(view)
-                }
-            default:
-                DispatchQueue.main.async {
-                    self.broadcaster.notify(alertMessageNotificationObserver.self, on: .main) {
-                        $0.alertMessageNotification(messageCont)
-                    }
+            DispatchQueue.main.async {
+                self.broadcaster.notify(alertMessageNotificationObserver.self, on: .main) {
+                    $0.alertMessageNotification(message)
                 }
                 }
             }
             }
         }
         }
 
 
+        // Read the color scheme preference from UserDefaults; defaults to system default setting
+        @AppStorage("colorSchemePreference") private var colorSchemePreference: ColorSchemeOption = .systemDefault
+
         private func showSwiftMessage(_ message: MessageContent) {
         private func showSwiftMessage(_ message: MessageContent) {
             // SwiftMessages.pauseBetweenMessages = 1.0
             // SwiftMessages.pauseBetweenMessages = 1.0
+
+            if snoozeUntilDate > Date(), message.action == .snooze {
+                return
+            }
+
             var config = SwiftMessages.defaultConfig
             var config = SwiftMessages.defaultConfig
             let view = MessageView.viewFromNib(layout: .cardView)
             let view = MessageView.viewFromNib(layout: .cardView)
 
 
+//            viewRespectsSystemMinimumLayoutMargins = false
+            view.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
+            config.prefersStatusBarHidden = true
+
+            // Set id so that multiple notifications are not queued while waiting for user response; only the latest will be shown
+            if message.subtype == .glucose || message.subtype == .carb {
+                view.id = message.type.rawValue + message.subtype.rawValue
+            }
+
             let titleContent: String
             let titleContent: String
 
 
+            let iconName = UIApplication.shared.alternateIconName ?? "trioBlack"
+            let iconImage = UIImage(named: iconName) ?? UIImage()
+
             view.configureContent(
             view.configureContent(
                 title: "title",
                 title: "title",
                 body: NSLocalizedString(message.content, comment: "Info message"),
                 body: NSLocalizedString(message.content, comment: "Info message"),
@@ -139,81 +218,42 @@ extension Main {
                 buttonTapHandler: nil
                 buttonTapHandler: nil
             )
             )
 
 
+            view.configureIcon(withSize: CGSize(width: 40, height: 40), contentMode: .scaleAspectFit)
+            view.iconImageView!.image = iconImage
+            view.iconImageView?.layer.cornerRadius = 10
+
+            view.customConfigureTheme(
+                colorSchemePreference: colorSchemePreference
+            )
+
+            view.iconImageView?.image = iconImage
+
             switch message.type {
             switch message.type {
             case .info,
             case .info,
                  .other:
                  .other:
-                view.backgroundColor = .secondarySystemGroupedBackground
-                config.duration = .automatic
+                config.duration = .seconds(seconds: 5)//.automatic
                 titleContent = message.title != "" ? message.title : NSLocalizedString("Info", comment: "Info title")
                 titleContent = message.title != "" ? message.title : NSLocalizedString("Info", comment: "Info title")
             case .warning:
             case .warning:
-                view.configureTheme(.warning, iconStyle: .subtle)
                 config.duration = .forever
                 config.duration = .forever
-                view.button?.setImage(Icon.warningSubtle.image, for: .normal)
                 titleContent = message.title != "" ? message
                 titleContent = message.title != "" ? message
                     .title : NSLocalizedString("Warning", comment: "Warning title")
                     .title : NSLocalizedString("Warning", comment: "Warning title")
-                view.buttonTapHandler = { _ in
-                    SwiftMessages.hide()
-                }
-            case .errorPump:
-                view.configureTheme(.error, iconStyle: .subtle)
+            case .error:
                 config.duration = .forever
                 config.duration = .forever
-                view.button?.setImage(Icon.errorSubtle.image, for: .normal)
                 titleContent = message.title != "" ? message
                 titleContent = message.title != "" ? message
                     .title : NSLocalizedString("Error", comment: "Error title")
                     .title : NSLocalizedString("Error", comment: "Error title")
-                view.buttonTapHandler = { _ in
-                    SwiftMessages.hide()
-                    // display the pump configuration immediatly
-                    if let pump = self.provider.deviceManager.pumpManager,
-                       let bluetooth = self.provider.bluetoothProvider
-                    {
-                        let view = PumpConfig.PumpSettingsView(
-                            pumpManager: pump,
-                            bluetoothManager: bluetooth,
-                            completionDelegate: self
-                        ).asAny()
-                        self.router.mainSecondaryModalView.send(view)
-                    }
-                }
-            case .alertPermissionWarning:
-                view.configureTheme(.error, iconStyle: .none)
-                config.duration = .forever
+            }
 
 
-                view.iconLabel = nil
-                view.iconImageView = nil
-                let disclosureIndicator = UIImage(systemName: "chevron.right")?.withTintColor(.white)
-                view.button?.setImage(disclosureIndicator, for: .normal)
-                view.button?.backgroundColor = UIColor.red
-                view.button?.tintColor = UIColor.white
+            view.titleLabel?.text = titleContent
+            config.dimMode = .gray(interactive: true)
 
 
-                titleContent = message.title != "" ? message
-                    .title : NSLocalizedString("Error", comment: "Error title")
-                view.buttonTapHandler = { _ in
-                    SwiftMessages.hide()
-                    UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
-                }
-            case .pumpConfig:
-                titleContent = ""
-                if let pump = provider.deviceManager.pumpManager,
-                   let bluetooth = provider.bluetoothProvider
-                {
-                    let view = PumpConfig.PumpSettingsView(
-                        pumpManager: pump,
-                        bluetoothManager: bluetooth,
-                        completionDelegate: self
-                    ).asAny()
-                    router.mainSecondaryModalView.send(view)
-                }
+            setupAction(message: message, view: view)
+            if message.trigger != nil {
+                addOrReplaceTriggerTimer(message: message, config: config, view: view)
             }
             }
 
 
-            if message.type != .pumpConfig
-            {
-                view.titleLabel?.text = titleContent
-                config.dimMode = .gray(interactive: true)
-                // Show if not hidden
-                if !view.isHidden {
-                    SwiftMessages.show(config: config, view: view)
-                }
-            }
+            guard message.type == .error || message.action != .pumpConfig, message.trigger == nil, !view.isHidden else { return }
+
+            SwiftMessages.show(config: config, view: view)
         }
         }
 
 
         override func subscribe() {
         override func subscribe() {
@@ -237,7 +277,10 @@ extension Main {
             router.alertMessage
             router.alertMessage
                 .receive(on: DispatchQueue.main)
                 .receive(on: DispatchQueue.main)
                 .sink { message in
                 .sink { message in
-                    self.queueMessageIfNeeded(message)
+                    guard !self.isApnPumpConfigAction(message) else { return }
+                    guard self.allowNotify(message) else { return }
+//                    self.queueMessageIfNeeded(message) // TODO: Remove if Batched Info and Throttled APNs are NOT in-scope
+                    self.showAlertMessage(message) // TODO: Call this if Batched Info and Throttled APNs are NOT in-scope
                 }
                 }
                 .store(in: &lifetime)
                 .store(in: &lifetime)
 
 
@@ -260,6 +303,57 @@ extension Main {
     }
     }
 }
 }
 
 
+extension MessageView {
+    func currentColorScheme() -> ColorScheme {
+        let userInterfaceStyle = UITraitCollection.current.userInterfaceStyle
+        return userInterfaceStyle == .dark ? .dark : .light
+    }
+
+    func customConfigureTheme(colorSchemePreference: ColorSchemeOption) {
+        let defaultSystemColorScheme = currentColorScheme() // UIColor.defaultSystemBackgroundColor
+        var backgroundColor = UIColor.systemBackground
+        var foregroundColor = UIColor.white
+        // Color.gray.opacity(0.1) is used by MainRootView but is translucent
+        // lightGray is same color as MainRootView background and systemGray6 is a shade darker
+        // systemGray5 is a shade darker than systemGray6 and gray is a few shades darker
+        // systemBackground is the same as systemGray6 when iOS is light mode
+        switch colorSchemePreference {
+        case .systemDefault:
+            backgroundColor = defaultSystemColorScheme == .light ? UIColor.systemBackground :
+                UIColor(Color.black.opacity(0.9))
+            foregroundColor = UIColor.label
+        case .dark:
+            backgroundColor = defaultSystemColorScheme == .light ? UIColor.systemGray5 :
+                UIColor(Color.black.opacity(0.9))
+            foregroundColor = defaultSystemColorScheme == .light ? UIColor.black : UIColor.white
+        case .light:
+            backgroundColor = defaultSystemColorScheme == .light ? .systemGray6 : UIColor
+                .gray
+            foregroundColor = defaultSystemColorScheme == .light ? UIColor.black : UIColor.white
+        }
+
+        iconImageView?.tintColor = foregroundColor
+        backgroundView.backgroundColor = backgroundColor
+        titleLabel?.textColor = foregroundColor
+        bodyLabel?.textColor = foregroundColor
+        iconImageView?.isHidden = iconImageView?.image == nil
+
+        backgroundView.layer.cornerRadius = 25
+
+        let adjustedFont = UIFont.systemFont(ofSize: 13.0, weight: .bold)
+        let preferredTitleFont = UIFontMetrics(forTextStyle: .footnote).scaledFont(for: adjustedFont)
+        let preferredBodyFont = UIFont.preferredFontforStyle(forTextStyle: .footnote)
+        // Set the title and body font to the dynamic type sizes
+        titleLabel?.adjustsFontForContentSizeCategory = true
+        titleLabel?.font = preferredTitleFont
+        bodyLabel?.adjustsFontForContentSizeCategory = true
+        bodyLabel?.font = preferredBodyFont
+        // Set custom colors for title and body text
+        titleLabel?.textColor = foregroundColor
+        bodyLabel?.textColor = foregroundColor
+    }
+}
+
 @available(iOS 16.0, *)
 @available(iOS 16.0, *)
 extension Main.StateModel: CompletionDelegate {
 extension Main.StateModel: CompletionDelegate {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
     func completionNotifyingDidComplete(_: CompletionNotifying) {
@@ -267,3 +361,12 @@ extension Main.StateModel: CompletionDelegate {
         router.mainSecondaryModalView.send(nil)
         router.mainSecondaryModalView.send(nil)
     }
     }
 }
 }
+
+// Extension to convert SwiftUI TextStyle to UIFont
+extension UIFont {
+    static func preferredFontforStyle(forTextStyle: UIFont.TextStyle) -> UIFont {
+        let uiFontMetrics = UIFontMetrics.default
+        let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: forTextStyle)
+        return uiFontMetrics.scaledFont(for: UIFont(descriptor: descriptor, size: 0)) // size: 12
+    }
+}

+ 7 - 2
FreeAPS/Sources/Modules/Settings/SettingItems.swift

@@ -200,10 +200,15 @@ enum SettingItems {
     ]
     ]
 
 
     static let notificationItems = [
     static let notificationItems = [
+        SettingItem(title: "Manage iOS Preferences", view: .notificationSettings),
         SettingItem(
         SettingItem(
-            title: "Glucose Notifications",
+            title: "Trio Notifications",
             view: .glucoseNotificationSettings,
             view: .glucoseNotificationSettings,
             searchContents: [
             searchContents: [
+                "Always Notify Pump",
+                "Always Notify CGM",
+                "Always Notify Carb",
+                "Always Notify Algorithm",
                 "Show Glucose App Badge",
                 "Show Glucose App Badge",
                 "Always Notify Glucose",
                 "Always Notify Glucose",
                 "Play Alarm Sound",
                 "Play Alarm Sound",
@@ -211,7 +216,7 @@ enum SettingItems {
                 "Low Glucose Alarm Limit",
                 "Low Glucose Alarm Limit",
                 "High Glucose Alarm Limit"
                 "High Glucose Alarm Limit"
             ],
             ],
-            path: ["Notifications", "Glucose Notifications"]
+            path: ["Notifications", "Trio Notifications"] // Glucose
         ),
         ),
         SettingItem(
         SettingItem(
             title: "Live Activity",
             title: "Live Activity",

+ 79 - 15
FreeAPS/Sources/Modules/Settings/View/Subviews/NotificationsView.swift

@@ -14,8 +14,13 @@ struct NotificationsView: BaseView {
 
 
     @ObservedObject var state: Settings.StateModel
     @ObservedObject var state: Settings.StateModel
     @State var notificationsDisabled = false
     @State var notificationsDisabled = false
+    @State var showAlert = false
+    @State private var shouldDisplayHint: Bool = false
+    @State var hintDetent = PresentationDetent.large
+    @State var selectedVerboseHint: String? =
+        "Notifications give you important Trio information without requiring you to open the app.\n\nKeep these turned ON in your phone’s settings to ensure you receive Trio Notifications, Critical Alerts, and Time Sensitive Notifications."
+    @State var hintLabel: String? = "Manage iOS Preferences"
 
 
-    @Environment(\.appName) private var appName
     @Environment(\.colorScheme) var colorScheme
     @Environment(\.colorScheme) var colorScheme
     var color: LinearGradient {
     var color: LinearGradient {
         colorScheme == .dark ? LinearGradient(
         colorScheme == .dark ? LinearGradient(
@@ -37,18 +42,46 @@ struct NotificationsView: BaseView {
     var body: some View {
     var body: some View {
         Form {
         Form {
             Section(
             Section(
+                header: Text("Manage iOS Preferences"),
+                content: {
+                    manageNotifications
+                }
+            )
+            Section {
+                VStack {
+                    notificationsEnabledStatus
+                    HStack(alignment: .top) {
+                        Text(
+                            "Notifications give you important Trio information without requiring you to open the app."
+                        )
+                        .font(.footnote)
+                        .foregroundColor(.secondary)
+                        .lineLimit(nil)
+                        Spacer()
+                        Button(
+                            action: {
+                                hintLabel = "Manage iOS Preferences"
+                                selectedVerboseHint =
+                                    "Notifications give you important Trio information without requiring you to open the app.\n\nKeep these turned ON in your phone’s settings to ensure you receive Trio Notifications, Critical Alerts, and Time Sensitive Notifications."
+                                shouldDisplayHint.toggle()
+                            },
+                            label: {
+                                HStack {
+                                    Image(systemName: "questionmark.circle")
+                                }
+                            }
+                        ).buttonStyle(BorderlessButtonStyle())
+                    }.padding(.top)
+                }.padding(.bottom)
+            }.listRowBackground(Color.chart)
+            Section(
                 header: Text("Notification Center"),
                 header: Text("Notification Center"),
                 content: {
                 content: {
-                    Section(footer: DescriptiveText(label: String(format: NSLocalizedString("""
-                    Notifications give you important %1$@ app information without requiring you to open the app.
-
-                    Keep these turned ON in your phone’s settings to ensure you receive %1$@ Notifications, Critical Alerts, and Time Sensitive Notifications.
-                    """, comment: "Alert Permissions descriptive text (1: app name)"), appName)))
-                        {
-                            manageNotifications
-                            notificationsEnabledStatus
-                        }
-                    Text("Glucose Notifications").navigationLink(to: .glucoseNotificationSettings, from: self)
+//                    if !notificationsDisabled {
+                    // Text("Glucose Notifications").navigationLink(to: .glucoseNotificationSettings, from: self)//TODO: add MessageType settings
+                    Text("Trio Notifications")
+                        .navigationLink(to: .glucoseNotificationSettings, from: self) // Glucose Notifications
+//                    }
 
 
                     if #available(iOS 16.2, *) {
                     if #available(iOS 16.2, *) {
                         Text("Live Activity").navigationLink(to: .liveActivitySettings, from: self)
                         Text("Live Activity").navigationLink(to: .liveActivitySettings, from: self)
@@ -59,9 +92,30 @@ struct NotificationsView: BaseView {
             )
             )
             .listRowBackground(Color.chart)
             .listRowBackground(Color.chart)
         }
         }
-        .onReceive(resolver.resolve(AlertPermissionsChecker.self)!.$notificationsDisabled, perform: {
-            notificationsDisabled = $0
-        })
+        .onReceive(
+            resolver.resolve(AlertPermissionsChecker.self)!.$notificationsDisabled,
+            perform: {
+                if notificationsDisabled != $0 {
+                    notificationsDisabled = $0
+                    if notificationsDisabled {
+                        showAlert = true
+                    }
+                }
+            }
+        )
+        .alert(
+            isPresented: self.$showAlert,
+            content: { self.notificationReminder() }
+        )
+        .sheet(isPresented: $shouldDisplayHint) {
+            SettingInputHintView(
+                hintDetent: $hintDetent,
+                shouldDisplayHint: $shouldDisplayHint,
+                hintLabel: hintLabel ?? "",
+                hintText: selectedVerboseHint ?? "",
+                sheetTitle: "Help"
+            )
+        }
         .scrollContentBackground(.hidden).background(color)
         .scrollContentBackground(.hidden).background(color)
         .navigationTitle("Notifications")
         .navigationTitle("Notifications")
         .navigationBarTitleDisplayMode(.automatic)
         .navigationBarTitleDisplayMode(.automatic)
@@ -69,6 +123,16 @@ struct NotificationsView: BaseView {
 }
 }
 
 
 extension NotificationsView {
 extension NotificationsView {
+    func notificationReminder() -> Alert {
+        Alert(
+            title: Text("\u{2757} Notifications are Required"),
+            message: Text(
+                "Please authorize notifications by tapping 'Open iOS Settings' > 'Notifications' and enable 'Allow Notifications' for 'Notification Center' and 'Banners' Alerts."
+            ),
+            dismissButton: .default(Text("Ok"))
+        )
+    }
+
     @ViewBuilder private func onOff(_ val: Bool) -> some View {
     @ViewBuilder private func onOff(_ val: Bool) -> some View {
         if val {
         if val {
             Text(NSLocalizedString("On", comment: "Notification Setting Status is On"))
             Text(NSLocalizedString("On", comment: "Notification Setting Status is On"))
@@ -83,7 +147,7 @@ extension NotificationsView {
     private var manageNotifications: some View {
     private var manageNotifications: some View {
         Button(action: { UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) }) {
         Button(action: { UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) }) {
             HStack {
             HStack {
-                Text(NSLocalizedString("Manage Permissions in Settings", comment: "Manage Permissions in Settings button text"))
+                Text(NSLocalizedString("Open iOS Settings", comment: "Manage Permissions in Settings button text"))
                 Spacer()
                 Spacer()
                 Image(systemName: "chevron.right").foregroundColor(.gray).font(.footnote)
                 Image(systemName: "chevron.right").foregroundColor(.gray).font(.footnote)
             }
             }

+ 14 - 5
FreeAPS/Sources/Router/Router.swift

@@ -2,20 +2,30 @@ import Combine
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
-enum MessageType {
+enum MessageType: String {
     case info
     case info
     case warning
     case warning
-    case errorPump
-    case pumpConfig
-    case alertPermissionWarning
+    case error
     case other
     case other
 }
 }
 
 
+enum MessageSubtype: String {
+    case pump
+    case cgm
+    case carb
+    case glucose
+    case algorithm
+    case misc
+}
+
 struct MessageContent {
 struct MessageContent {
     var content: String
     var content: String
     var type: MessageType = .info
     var type: MessageType = .info
+    var subtype: MessageSubtype = .misc
     var title: String = ""
     var title: String = ""
     var useAPN: Bool = true
     var useAPN: Bool = true
+    var trigger: UNNotificationTrigger? = nil
+    var action: NotificationAction = .none
 }
 }
 
 
 protocol Router {
 protocol Router {
@@ -29,7 +39,6 @@ final class BaseRouter: Router {
     let mainModalScreen = CurrentValueSubject<Screen?, Never>(nil)
     let mainModalScreen = CurrentValueSubject<Screen?, Never>(nil)
     let mainSecondaryModalView = CurrentValueSubject<AnyView?, Never>(nil)
     let mainSecondaryModalView = CurrentValueSubject<AnyView?, Never>(nil)
     let alertMessage = PassthroughSubject<MessageContent, Never>()
     let alertMessage = PassthroughSubject<MessageContent, Never>()
-
     private let resolver: Resolver
     private let resolver: Resolver
 
 
     init(resolver: Resolver) {
     init(resolver: Resolver) {

+ 6 - 6
FreeAPS/Sources/Services/Notifications/AlertPermissionsChecker.swift

@@ -4,17 +4,17 @@ import LoopKit
 import SwiftUI
 import SwiftUI
 import Swinject
 import Swinject
 
 
-protocol AlertPermissionsCheckerDelegate: AnyObject {
-    func notificationsPermissions(requiresRiskMitigation: Bool, scheduledDeliveryEnabled: Bool)
-}
+// protocol AlertPermissionsCheckerDelegate: AnyObject {
+//    func notificationsPermissions(requiresRiskMitigation: Bool, scheduledDeliveryEnabled: Bool)
+// }
 
 
 public class AlertPermissionsChecker: ObservableObject, Injectable {
 public class AlertPermissionsChecker: ObservableObject, Injectable {
-    @Environment(\.appName) private var appName
+//    @Environment(\.appName) private var appName
     private lazy var cancellables = Set<AnyCancellable>()
     private lazy var cancellables = Set<AnyCancellable>()
     private var listeningToNotificationCenter = false
     private var listeningToNotificationCenter = false
 
 
-    @Injected() private var apsManager: APSManager!
-    @Injected() private var router: Router!
+//    @Injected() private var apsManager: APSManager!
+//    @Injected() private var router: Router!
     @Published var notificationsDisabled: Bool = false
     @Published var notificationsDisabled: Bool = false
 
 
     init(resolver: Resolver) {
     init(resolver: Resolver) {

+ 69 - 41
FreeAPS/Sources/Services/UserNotifications/UserNotificationsManager.swift

@@ -21,6 +21,7 @@ enum NotificationAction: String {
 
 
     case snooze
     case snooze
     case pumpConfig
     case pumpConfig
+    case none
 }
 }
 
 
 protocol BolusFailureObserver {
 protocol BolusFailureObserver {
@@ -166,7 +167,7 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
             ),
             ),
             carbs
             carbs
         )
         )
-        addRequest(identifier: .carbsRequiredNotification, content: content, deleteOld: true)
+        addRequest(identifier: .carbsRequiredNotification, content: content, deleteOld: true, messageSubtype: .carb)
     }
     }
 
 
     private func scheduleMissingLoopNotifiactions(date _: Date) {
     private func scheduleMissingLoopNotifiactions(date _: Date) {
@@ -193,13 +194,17 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
             identifier: .noLoopFirstNotification,
             identifier: .noLoopFirstNotification,
             content: firstContent,
             content: firstContent,
             deleteOld: true,
             deleteOld: true,
-            trigger: firstTrigger
+            trigger: firstTrigger,
+            messageType: .error,
+            messageSubtype: .algorithm
         )
         )
         addRequest(
         addRequest(
             identifier: .noLoopSecondNotification,
             identifier: .noLoopSecondNotification,
             content: secondContent,
             content: secondContent,
             deleteOld: true,
             deleteOld: true,
-            trigger: secondTrigger
+            trigger: secondTrigger,
+            messageType: .error,
+            messageSubtype: .algorithm
         )
         )
     }
     }
 
 
@@ -218,7 +223,9 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
             identifier: .noLoopFirstNotification,
             identifier: .noLoopFirstNotification,
             content: content,
             content: content,
             deleteOld: true,
             deleteOld: true,
-            trigger: nil
+            trigger: nil,
+            messageType: .error,
+            messageSubtype: .pump
         )
         )
     }
     }
 
 
@@ -257,15 +264,18 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
 
 
             var titles: [String] = []
             var titles: [String] = []
             var notificationAlarm = false
             var notificationAlarm = false
+            var messageType = MessageType.info
 
 
             switch glucoseStorage.alarm {
             switch glucoseStorage.alarm {
             case .none:
             case .none:
                 titles.append(NSLocalizedString("Glucose", comment: "Glucose"))
                 titles.append(NSLocalizedString("Glucose", comment: "Glucose"))
             case .low:
             case .low:
                 titles.append(NSLocalizedString("LOWALERT!", comment: "LOWALERT!"))
                 titles.append(NSLocalizedString("LOWALERT!", comment: "LOWALERT!"))
+                messageType = MessageType.warning
                 notificationAlarm = true
                 notificationAlarm = true
             case .high:
             case .high:
                 titles.append(NSLocalizedString("HIGHALERT!", comment: "HIGHALERT!"))
                 titles.append(NSLocalizedString("HIGHALERT!", comment: "HIGHALERT!"))
+                messageType = MessageType.warning
                 notificationAlarm = true
                 notificationAlarm = true
             }
             }
 
 
@@ -291,7 +301,14 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
                     content.userInfo[NotificationAction.key] = NotificationAction.snooze.rawValue
                     content.userInfo[NotificationAction.key] = NotificationAction.snooze.rawValue
                 }
                 }
 
 
-                addRequest(identifier: .glucocoseNotification, content: content, deleteOld: true)
+                addRequest(
+                    identifier: .glucocoseNotification,
+                    content: content,
+                    deleteOld: true,
+                    messageType: messageType,
+                    messageSubtype: .glucose,
+                    action: NotificationAction.snooze
+                )
             }
             }
         } catch {
         } catch {
             debugPrint(
             debugPrint(
@@ -376,44 +393,29 @@ final class BaseUserNotificationsManager: NSObject, UserNotificationsManager, In
         }
         }
     }
     }
 
 
-    private func ensureCanSendNotification(_ completion: @escaping () -> Void) {
-        center.getNotificationSettings { settings in
-            guard settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional else {
-                warning(.service, "ensureCanSendNotification failed, authorization denied")
-                return
-            }
-
-            debug(.service, "Sending notification was allowed")
-
-            completion()
-        }
-    }
-
     private func addRequest(
     private func addRequest(
         identifier: Identifier,
         identifier: Identifier,
         content: UNMutableNotificationContent,
         content: UNMutableNotificationContent,
         deleteOld: Bool = false,
         deleteOld: Bool = false,
         trigger: UNNotificationTrigger? = nil,
         trigger: UNNotificationTrigger? = nil,
-        messageType: MessageType = MessageType.other
+        messageType: MessageType = MessageType.other,
+        messageSubtype: MessageSubtype = MessageSubtype.misc,
+        action: NotificationAction = NotificationAction.none
     ) {
     ) {
-        if alertPermissionsChecker.notificationsDisabled, trigger == nil {
-            if trigger != nil {
-                debug(.default, "TODO: Triggers are not supported by alertMessage")
-                return
-            }
+        if alertPermissionsChecker.notificationsDisabled {
             let messageCont = MessageContent(
             let messageCont = MessageContent(
                 content: content.body,
                 content: content.body,
                 type: messageType,
                 type: messageType,
+                subtype: messageSubtype,
                 title: content.title,
                 title: content.title,
-                useAPN: false
+                useAPN: false,
+                trigger: trigger,
+                action: action
             )
             )
             router.alertMessage.send(messageCont)
             router.alertMessage.send(messageCont)
             return
             return
         }
         }
-        let timestamp = Date().timeIntervalSince1970
-        let uniqueIdentifier = "\(identifier.rawValue)_\(timestamp)"
-        content.threadIdentifier = String(describing: messageType)
-        let request = UNNotificationRequest(identifier: uniqueIdentifier, content: content, trigger: trigger)
+        let request = UNNotificationRequest(identifier: identifier.rawValue, content: content, trigger: trigger)
 
 
         if deleteOld {
         if deleteOld {
             DispatchQueue.main.async {
             DispatchQueue.main.async {
@@ -483,16 +485,27 @@ extension BaseUserNotificationsManager: alertMessageNotificationObserver {
     func alertMessageNotification(_ message: MessageContent) {
     func alertMessageNotification(_ message: MessageContent) {
         let content = UNMutableNotificationContent()
         let content = UNMutableNotificationContent()
 
 
-        switch message.type {
-        case .info:
-            content.title = NSLocalizedString("Info", comment: "Info title")
-        case .warning:
-            content.title = NSLocalizedString("Warning", comment: "Warning title")
-        case .errorPump:
-            content.title = NSLocalizedString("Error", comment: "Error title")
-        default:
+        if message.title == "" {
+            switch message.type {
+            case .info:
+                content.title = NSLocalizedString("Info", comment: "Info title")
+            case .warning:
+                content.title = NSLocalizedString("Warning", comment: "Warning title")
+            case .error: // .errorPump:
+                content.title = NSLocalizedString("Error", comment: "Error title")
+            default:
+                content.title = message.title
+            }
+        } else {
             content.title = message.title
             content.title = message.title
         }
         }
+        switch message.action {
+        case .snooze:
+            content.userInfo[NotificationAction.key] = NotificationAction.snooze.rawValue
+        case .pumpConfig:
+            content.userInfo[NotificationAction.key] = NotificationAction.pumpConfig.rawValue
+        default: break
+        }
 
 
         content.body = NSLocalizedString(message.content, comment: "Info message")
         content.body = NSLocalizedString(message.content, comment: "Info message")
         content.sound = .default
         content.sound = .default
@@ -500,8 +513,10 @@ extension BaseUserNotificationsManager: alertMessageNotificationObserver {
             identifier: .alertMessageNotification,
             identifier: .alertMessageNotification,
             content: content,
             content: content,
             deleteOld: true,
             deleteOld: true,
-            trigger: nil,
-            messageType: message.type
+            trigger: message.trigger,
+            messageType: message.type,
+            messageSubtype: message.subtype,
+            action: message.action
         )
         )
     }
     }
 }
 }
@@ -510,8 +525,12 @@ extension BaseUserNotificationsManager: pumpNotificationObserver {
     func pumpNotification(alert: AlertEntry) {
     func pumpNotification(alert: AlertEntry) {
         let content = UNMutableNotificationContent()
         let content = UNMutableNotificationContent()
         let alertUp = alert.alertIdentifier.uppercased()
         let alertUp = alert.alertIdentifier.uppercased()
+        let typeMessage: MessageType
         if alertUp.contains("FAULT") || alertUp.contains("ERROR") {
         if alertUp.contains("FAULT") || alertUp.contains("ERROR") {
             content.userInfo[NotificationAction.key] = NotificationAction.pumpConfig.rawValue
             content.userInfo[NotificationAction.key] = NotificationAction.pumpConfig.rawValue
+            typeMessage = .error
+        } else {
+            typeMessage = .warning
         }
         }
         content.title = alert.contentTitle ?? "Unknown"
         content.title = alert.contentTitle ?? "Unknown"
         content.body = alert.contentBody ?? "Unknown"
         content.body = alert.contentBody ?? "Unknown"
@@ -521,7 +540,9 @@ extension BaseUserNotificationsManager: pumpNotificationObserver {
             content: content,
             content: content,
             deleteOld: true,
             deleteOld: true,
             trigger: nil,
             trigger: nil,
-            messageType: MessageType.errorPump
+            messageType: typeMessage,
+            messageSubtype: .pump,
+            action: .pumpConfig
         )
         )
     }
     }
 
 
@@ -575,8 +596,15 @@ extension BaseUserNotificationsManager: UNUserNotificationCenterDelegate {
         case .snooze:
         case .snooze:
             router.mainModalScreen.send(.snooze)
             router.mainModalScreen.send(.snooze)
         case .pumpConfig:
         case .pumpConfig:
-            let messageCont = MessageContent(content: response.notification.request.content.body, type: MessageType.pumpConfig)
+            let messageCont = MessageContent(
+                content: response.notification.request.content.body,
+                type: MessageType.other,
+                subtype: .pump,
+                useAPN: false,
+                action: .pumpConfig
+            )
             router.alertMessage.send(messageCont)
             router.alertMessage.send(messageCont)
+        default: break
         }
         }
     }
     }
 }
 }