From d7e33859eee7c9e2c4f86de37bbd703e7f30c020 Mon Sep 17 00:00:00 2001 From: ETolboom Date: Wed, 22 Apr 2026 01:18:49 +0200 Subject: [PATCH 1/6] Improvements to critical alarm handling Add Do Not Disturb override toggle to each schedule Add banner for requesting critical alert permissions if shouldOverrideDoNotDisturb is true Move Critical Alert Volume into Alarm Settings view --- Common/Settings/GlucoseSchedules.swift | 16 +++- LibreTransmitter/NotificationHelper.swift | 60 +++++-------- .../NotificationHelperOverride.swift | 4 +- .../AlarmSettings/AlarmSettingsView.swift | 58 ++++++++++++- .../CriticalAlarmsVolumeView.swift | 85 +++++++++++++------ .../Views/Settings/SettingsView.swift | 6 -- 6 files changed, 155 insertions(+), 74 deletions(-) diff --git a/Common/Settings/GlucoseSchedules.swift b/Common/Settings/GlucoseSchedules.swift index 528d31a..6cd30d1 100644 --- a/Common/Settings/GlucoseSchedules.swift +++ b/Common/Settings/GlucoseSchedules.swift @@ -138,6 +138,19 @@ class GlucoseScheduleList: Codable, CustomStringConvertible { } return .none } + + public func shouldOverrideDoNotDisturb(_ currentGlucoseInMGDL: Double) -> Bool { + for schedule in activeSchedules { + let isAlarmingLow = (schedule.lowAlarm != nil && currentGlucoseInMGDL <= schedule.lowAlarm!) + let isAlarmingHigh = (schedule.highAlarm != nil && currentGlucoseInMGDL >= schedule.highAlarm!) + + if (isAlarmingLow || isAlarmingHigh) && (schedule.overrideDoNotDisturb == true) { + return true + } + } + + return false + } } class GlucoseSchedule: Codable, CustomStringConvertible { @@ -145,6 +158,7 @@ class GlucoseSchedule: Codable, CustomStringConvertible { var to: DateComponents? var lowAlarm: Double? var highAlarm: Double? + var overrideDoNotDisturb: Bool? var enabled: Bool? // glucose schedules are stored as standalone datecomponents (i.e. offsets) @@ -218,6 +232,6 @@ class GlucoseSchedule: Codable, CustomStringConvertible { } var description: String { - "(from: \(String(describing: from)), to: \(String(describing: to)), low: \(String(describing: lowAlarm)), high: \(String(describing: highAlarm)), enabled: \(String(describing: enabled)))" + "(from: \(String(describing: from)), to: \(String(describing: to)), low: \(String(describing: lowAlarm)), high: \(String(describing: highAlarm)), overrideDoNotDisturb: \(String(describing: overrideDoNotDisturb)), enabled: \(String(describing: enabled)))" } } diff --git a/LibreTransmitter/NotificationHelper.swift b/LibreTransmitter/NotificationHelper.swift index 12d9f90..127fbef 100644 --- a/LibreTransmitter/NotificationHelper.swift +++ b/LibreTransmitter/NotificationHelper.swift @@ -30,13 +30,8 @@ public enum NotificationHelper { case calibrationOngoing = "com.loopkit.libremiaomiao.calibration-notification" case libre2directFinishedSetup = "com.loopkit.libremiaomiao.libre2direct-notification" } - - public static var shouldRequestCriticalPermissions = false - - // don't touch this please - public static var criticalAlarmsEnabled = false - - + + public private(set) static var criticalAlarmsEnabled = false private static func vibrate(times: Int=3) { guard times >= 0 else { @@ -52,24 +47,6 @@ public enum NotificationHelper { public static func GlucoseUnitIsSupported(unit: HKUnit) -> Bool { [HKUnit.milligramsPerDeciliter, HKUnit.millimolesPerLiter].contains(unit) } - - private static func requestCriticalNotificationPermissions() { - logger.debug("\(#function) called") - let center = UNUserNotificationCenter.current() - center.requestAuthorization(options: [.badge, .sound, .alert, .criticalAlert]) { (granted, error) in - if granted { - logger.debug("\(#function) was granted") - UNUserNotificationCenter.current().getNotificationSettings { settings in - logPermissions(settings) - criticalAlarmsEnabled = settings.criticalAlertSetting == .enabled - } - } else { - logger.debug("\(#function) failed because of error: \(String(describing: error))") - } - - } - - } private static func logPermissions(_ settings: UNNotificationSettings, caller: String = #function) { @@ -77,21 +54,27 @@ public enum NotificationHelper { } - public static func requestNotificationPermissionsIfNeeded() { - // We assume loop will request necessary "non-critical" permissions for us - // So we are only interested in the "critical" permissions here - + public static func checkCriticalAlertStatus(completion: @escaping (Bool) -> Void) { UNUserNotificationCenter.current().getNotificationSettings { settings in - criticalAlarmsEnabled = settings.criticalAlertSetting == .enabled + let enabled = (settings.criticalAlertSetting == .enabled) + criticalAlarmsEnabled = enabled logPermissions(settings) - - if shouldRequestCriticalPermissions || NotificationHelperOverride.shouldOverrideRequestCriticalPermissions { - requestCriticalNotificationPermissions() + DispatchQueue.main.async { + completion(enabled) } - } } + public static func requestCriticalAlertPermission(completion: @escaping (Bool) -> Void) { + UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert, .criticalAlert]) { _, _ in + checkCriticalAlertStatus(completion: completion) + } + } + + public static func requestNotificationPermissionsIfNeeded() { + checkCriticalAlertStatus { _ in } + } + private static func ensureCanSendNotification(_ completion: @escaping () -> Void ) { UNUserNotificationCenter.current().getNotificationSettings { settings in guard settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional else { @@ -348,6 +331,7 @@ public extension NotificationHelper { let alarm = schedules?.getActiveAlarms(glucose.glucoseDouble) ?? .none let isSnoozed = GlucoseScheduleList.isSnoozed() + let shouldOverrideDoNotDisturb = schedules?.shouldOverrideDoNotDisturb(glucose.glucoseDouble) ?? false let shouldShowPhoneBattery = UserDefaults.standard.mmShowPhoneBattery let transmitterBattery = UserDefaults.standard.mmShowTransmitterBattery && battery != nil ? battery : nil @@ -359,7 +343,8 @@ public extension NotificationHelper { if shouldSend || alarm.isAlarming() { sendGlucoseNotification(glucose: glucose, oldValue: oldValue, glucoseFormatter: glucoseFormatter, - alarm: alarm, isSnoozed: isSnoozed, + alarm: alarm, shouldOverrideDoNotDisturb: shouldOverrideDoNotDisturb, + isSnoozed: isSnoozed, trend: trend, showPhoneBattery: shouldShowPhoneBattery, transmitterBattery: transmitterBattery) } else { @@ -371,6 +356,7 @@ public extension NotificationHelper { private static func sendGlucoseNotification(glucose: LibreGlucose, oldValue: LibreGlucose?, glucoseFormatter: QuantityFormatter, alarm: GlucoseScheduleAlarmResult = .none, + shouldOverrideDoNotDisturb: Bool = false, isSnoozed: Bool = false, trend: GlucoseTrend?, showPhoneBattery: Bool = false, @@ -387,10 +373,10 @@ public extension NotificationHelper { titles.append("Glucose") case .low: titles.append("LOWALERT!") - isCritical = true + isCritical = shouldOverrideDoNotDisturb case .high: titles.append("HIGHALERT!") - isCritical = true + isCritical = shouldOverrideDoNotDisturb } if isSnoozed { diff --git a/LibreTransmitter/NotificationHelperOverride.swift b/LibreTransmitter/NotificationHelperOverride.swift index 80daa2c..b445d68 100644 --- a/LibreTransmitter/NotificationHelperOverride.swift +++ b/LibreTransmitter/NotificationHelperOverride.swift @@ -7,8 +7,8 @@ // import Foundation -enum NotificationHelperOverride { - static var shouldOverrideRequestCriticalPermissions : Bool { +public enum NotificationHelperOverride { + public static var shouldOverrideRequestCriticalPermissions : Bool { // if you want LibreTransmitter to try upgrading to critical notifications, change this false } diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift index 0467ba4..d9e7b66 100644 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift @@ -8,6 +8,7 @@ import SwiftUI import HealthKit +import LibreTransmitter private func systemImage(_ name:String) -> some View { Image(systemName: name) @@ -24,6 +25,7 @@ class AlarmScheduleState: ObservableObject, Identifiable, Hashable { @Published var lowmgdl: Double = 72 @Published var highmgdl: Double = 180 @Published var enabled: Bool? = false + @Published var overrideDoNotDisturb: Bool? = false @Published var alarmDateComponents: AlarmTimeCellExternalState = AlarmTimeCellExternalState() @@ -114,6 +116,7 @@ class AlarmSettingsState: ObservableObject { schedule.enabled = storedState.schedules[i].enabled schedule.lowmgdl = storedState.schedules[i].lowAlarm ?? -1 schedule.highmgdl = storedState.schedules[i].highAlarm ?? -1 + schedule.overrideDoNotDisturb = storedState.schedules[i].overrideDoNotDisturb schedule.alarmDateComponents.startComponents = storedState.schedules[i].from schedule.alarmDateComponents.endComponents = storedState.schedules[i].to @@ -138,7 +141,9 @@ class AlarmSettingsState: ObservableObject { glucoseSchedule.highAlarm = newStateSchedule.highmgdl glucoseSchedule.from = newStateSchedule.alarmDateComponents.startComponents glucoseSchedule.to = newStateSchedule.alarmDateComponents.endComponents - + glucoseSchedule.overrideDoNotDisturb = newStateSchedule.overrideDoNotDisturb + + legacyState.schedules.append(glucoseSchedule) } @@ -285,6 +290,33 @@ struct AlarmHighRow: View { } } +struct OverrideDoNotDisturbRow: View { + @ObservedObject var schedule: AlarmScheduleState + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + HStack(alignment: .center) { + systemImage("bell.badge.fill") + .frame(maxWidth: 50, alignment: .leading) + Text("Override Do Not Disturb") + .frame(maxWidth: .infinity, alignment: .leading) + + Toggle("", isOn: Binding( + get: { schedule.overrideDoNotDisturb == true }, + set: { schedule.overrideDoNotDisturb = $0 } + )) + .frame(maxWidth: 50, alignment: .trailing) + } + + if schedule.overrideDoNotDisturb == true { + Text("This alarm will sound even in Do Not Disturb mode") + .font(.caption) + .foregroundColor(.secondary) + } + } + } +} + struct AlarmSettingsView: View { private(set) var glucoseUnit: HKUnit @@ -304,6 +336,12 @@ struct AlarmSettingsView: View { // for accessing the alarm section @State private var requiresAuthentication = Features.alarmSettingsViewRequiresAuthentication + @State private var criticalAlertsEnabled = false + + private var hasAnyScheduleWithOverride: Bool { + alarmState.schedules.contains { $0.overrideDoNotDisturb == true } + } + var body: some View { erasedWithKeyboardDismissal(list) .alert(item: $presentableStatus) { status in @@ -318,10 +356,18 @@ struct AlarmSettingsView: View { } } + checkCriticalAlertStatus() + } .disabled(requiresAuthentication ? !authSuccess : false) } + private func checkCriticalAlertStatus() { + NotificationHelper.checkCriticalAlertStatus { enabled in + criticalAlertsEnabled = enabled + } + } + func erasedWithKeyboardDismissal(_ view: any View) -> AnyView { if #available(iOS 16.0, *) { return AnyView(view.scrollDismissesKeyboard(.immediately)) @@ -333,19 +379,27 @@ struct AlarmSettingsView: View { @StateObject var errorReporter = FormErrorState() var list: some View { - List { + CriticalAlertsBannerSection(criticalAlertsEnabled: $criticalAlertsEnabled) + ForEach(Array(alarmState.schedules.enumerated()), id: \.1) { i, schedule in Section(header: Text(LocalizedString("Schedule ", comment: "Text describing schedule in alarmsettingsview") + "\(i+1)")) { AlarmDateRow(schedule: schedule, tag: i, subviewSelection: $subviewSelection) AlarmLowRow(schedule: schedule, glucoseUnit: glucoseUnit, glucoseUnitDesc: glucoseUnitDesc, errorReporter: errorReporter) AlarmHighRow(schedule: schedule, glucoseUnit: glucoseUnit, glucoseUnitDesc: glucoseUnitDesc, errorReporter: errorReporter) + if criticalAlertsEnabled { + OverrideDoNotDisturbRow(schedule: schedule) + } }.onTapGesture { self.hideKeyboardPreIos16() } } + + if criticalAlertsEnabled && hasAnyScheduleWithOverride { + CriticalAlarmsVolumeSection() + } Section { Button("Save") { diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift index cf76b4f..9846d31 100644 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift +++ b/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift @@ -7,41 +7,74 @@ // import SwiftUI +import LibreTransmitter -struct CriticalAlarmsVolumeView: View { +struct CriticalAlertsBannerSection: View { + @Binding var criticalAlertsEnabled: Bool + @State private var presentableStatus: StatusMessage? - private var intVolume : Int { - Int(mmCriticalAlarmsVolume) - } - @State private var isEditing = false - - private enum Key: String { - case mmCriticalAlarmsVolume = "com.loopkit.libreCriticalAlarmsVolume" - } - - @AppStorage(Key.mmCriticalAlarmsVolume.rawValue) var mmCriticalAlarmsVolume: Double = 60 - var body: some View { - List { - Section(header: Text("Critical alarm volume"), footer: Text("Critical alarms will always be sent with volume at minimum 60%")) { - Slider( - value: $mmCriticalAlarmsVolume, - in: 60...100, - step: 5, - onEditingChanged: { editing in - isEditing = editing + if NotificationHelperOverride.shouldOverrideRequestCriticalPermissions && !criticalAlertsEnabled { + Section { + VStack(alignment: .leading, spacing: 8) { + Label("Critical Alerts", systemImage: "bell.badge") + .font(.headline) + Text("Enable critical alerts so glucose alarms can sound even when Do Not Disturb or silent mode is on.") + .font(.subheadline) + .foregroundColor(.secondary) + Button(action: requestCriticalAlerts) { + Text("Enable Critical Alerts") + .frame(maxWidth: .infinity) } + .buttonStyle(.borderedProminent) + .tint(.orange) + .padding(.top, 4) + } + .padding(.vertical, 4) + } + .alert(item: $presentableStatus) { status in + Alert(title: Text(status.title), message: Text(status.message), dismissButton: .default(Text("Got it!"))) + } + } + } + + private func requestCriticalAlerts() { + NotificationHelper.requestCriticalAlertPermission { enabled in + criticalAlertsEnabled = enabled + if !enabled { + presentableStatus = StatusMessage( + title: "Could Not Enable Critical Alerts", + message: "Critical alerts could not be enabled. Make sure your app is built with the critical alerts entitlement and that your provisioning profile supports it." ) - Text("\(intVolume)%") - .foregroundColor(isEditing ? .red : .blue) - } } } } -struct CriticalAlarmsVolumeView_Previews: PreviewProvider { - static var previews: some View { - CriticalAlarmsVolumeView() +struct CriticalAlarmsVolumeSection: View { + private enum Key: String { + case mmCriticalAlarmsVolume = "com.loopkit.libreCriticalAlarmsVolume" + } + + @AppStorage(Key.mmCriticalAlarmsVolume.rawValue) var mmCriticalAlarmsVolume: Double = 60 + @State private var isEditing = false + + private var intVolume: Int { + Int(mmCriticalAlarmsVolume) + } + + var body: some View { + Section(header: Text("Critical alarm volume"), footer: Text("Critical alarms will always be sent with volume at minimum 60%")) { + Slider( + value: $mmCriticalAlarmsVolume, + in: 60...100, + step: 5, + onEditingChanged: { editing in + isEditing = editing + } + ) + Text("\(intVolume)%") + .foregroundColor(isEditing ? .red : .blue) + } } } diff --git a/LibreTransmitterUI/Views/Settings/SettingsView.swift b/LibreTransmitterUI/Views/Settings/SettingsView.swift index ac1b6e1..ade7dc7 100644 --- a/LibreTransmitterUI/Views/Settings/SettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/SettingsView.swift @@ -265,12 +265,6 @@ struct SettingsView: View { SettingsItem(title: "Alarms") } - if NotificationHelper.criticalAlarmsEnabled { - NavigationLink(destination: CriticalAlarmsVolumeView()) { - SettingsItem(title: "Critical Alarms volume") - } - } - NavigationLink(destination: GlucoseSettingsView()) { SettingsItem(title: "Glucose Settings") } From f2bc27f3b7f7cb1ed356405fd7910349c66cac5d Mon Sep 17 00:00:00 2001 From: ETolboom <151847362+ETolboom@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:58:25 +0200 Subject: [PATCH 2/6] Update LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift index 9846d31..defc6dd 100644 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift +++ b/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift @@ -44,7 +44,7 @@ struct CriticalAlertsBannerSection: View { if !enabled { presentableStatus = StatusMessage( title: "Could Not Enable Critical Alerts", - message: "Critical alerts could not be enabled. Make sure your app is built with the critical alerts entitlement and that your provisioning profile supports it." + message: "Critical alerts were not enabled. If you denied the permission prompt, you can enable them in iOS Settings > Notifications for this app. If this is a development build, also make sure the app has the critical alerts entitlement and that the provisioning profile supports it." ) } } From 5d4622b0c99b6de6218a3b047adc0074d315d280 Mon Sep 17 00:00:00 2001 From: ETolboom <151847362+ETolboom@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:58:43 +0200 Subject: [PATCH 3/6] Update LibreTransmitter/NotificationHelperOverride.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- LibreTransmitter/NotificationHelperOverride.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LibreTransmitter/NotificationHelperOverride.swift b/LibreTransmitter/NotificationHelperOverride.swift index b445d68..e146a18 100644 --- a/LibreTransmitter/NotificationHelperOverride.swift +++ b/LibreTransmitter/NotificationHelperOverride.swift @@ -9,7 +9,8 @@ import Foundation public enum NotificationHelperOverride { public static var shouldOverrideRequestCriticalPermissions : Bool { - // if you want LibreTransmitter to try upgrading to critical notifications, change this + // if you want LibreTransmitter to override whether it shows the UI/banner for + // the critical notification permissions flow, change this false } } From a3e03d73d6d1c53a11fa8be99ff59d059cd19301 Mon Sep 17 00:00:00 2001 From: ETolboom Date: Wed, 22 Apr 2026 11:12:09 +0200 Subject: [PATCH 4/6] Add LocalizedString --- .../Views/Settings/AlarmSettings/AlarmSettingsView.swift | 4 ++-- .../Settings/AlarmSettings/CriticalAlarmsVolumeView.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift index d9e7b66..853d41b 100644 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift @@ -298,7 +298,7 @@ struct OverrideDoNotDisturbRow: View { HStack(alignment: .center) { systemImage("bell.badge.fill") .frame(maxWidth: 50, alignment: .leading) - Text("Override Do Not Disturb") + Text(LocalizedString("Override Do Not Disturb", comment: "Text describing that 'Do Not Disturb' will overriden by this alarm")) .frame(maxWidth: .infinity, alignment: .leading) Toggle("", isOn: Binding( @@ -309,7 +309,7 @@ struct OverrideDoNotDisturbRow: View { } if schedule.overrideDoNotDisturb == true { - Text("This alarm will sound even in Do Not Disturb mode") + Text(LocalizedString("This alarm will sound even in Do Not Disturb mode", comment: "Text that describes that the alarm will sound even in Do Not Disturb mode")) .font(.caption) .foregroundColor(.secondary) } diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift index defc6dd..7eb08f0 100644 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift +++ b/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift @@ -19,7 +19,7 @@ struct CriticalAlertsBannerSection: View { VStack(alignment: .leading, spacing: 8) { Label("Critical Alerts", systemImage: "bell.badge") .font(.headline) - Text("Enable critical alerts so glucose alarms can sound even when Do Not Disturb or silent mode is on.") + Text(LocalizedString("Enable critical alerts so glucose alarms can sound even when Do Not Disturb or silent mode is on.", comment: "Text describing the functionality of the 'Do Not Disturb' toggle")) .font(.subheadline) .foregroundColor(.secondary) Button(action: requestCriticalAlerts) { @@ -64,7 +64,7 @@ struct CriticalAlarmsVolumeSection: View { } var body: some View { - Section(header: Text("Critical alarm volume"), footer: Text("Critical alarms will always be sent with volume at minimum 60%")) { + Section(header: Text(LocalizedString("Critical alarm volume", comment: "Header describing the volume of the critical alerts")), footer: Text(LocalizedString("Critical alarms will always be sent with volume at minimum 60%", comment: "Text describing that critical alerts are sent at a minimum volume of 60%"))) { Slider( value: $mmCriticalAlarmsVolume, in: 60...100, From 69aec75183d41654cd93bdcff185e1671b4c7b54 Mon Sep 17 00:00:00 2001 From: ETolboom Date: Wed, 22 Apr 2026 11:22:51 +0200 Subject: [PATCH 5/6] Merge CriticalAlarmsVolumeView (CriticalAlarmsVolumeSection and CriticalAlertsBannerSection) into AlarmSettingsView --- LibreTransmitter.xcodeproj/project.pbxproj | 4 - .../AlarmSettings/AlarmSettingsView.swift | 70 ++++++++++++++++ .../CriticalAlarmsVolumeView.swift | 80 ------------------- 3 files changed, 70 insertions(+), 84 deletions(-) delete mode 100644 LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift diff --git a/LibreTransmitter.xcodeproj/project.pbxproj b/LibreTransmitter.xcodeproj/project.pbxproj index 7be9de6..3354331 100644 --- a/LibreTransmitter.xcodeproj/project.pbxproj +++ b/LibreTransmitter.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 2746C73F26DCF83700E31BD9 /* Features.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C73D26DCF83400E31BD9 /* Features.swift */; }; 2746C74226DD0F8800E31BD9 /* Libre2DirectSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2746C74126DD0F8800E31BD9 /* Libre2DirectSetup.swift */; }; 274E71D3297ED77300FCFECD /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274E71D2297ED77300FCFECD /* AuthView.swift */; }; - 274E71D52986D4A600FCFECD /* CriticalAlarmsVolumeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274E71D42986D4A600FCFECD /* CriticalAlarmsVolumeView.swift */; }; 275786AB26753CC400845D0E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 275786AA26753CC400845D0E /* SettingsView.swift */; }; 275EC993265AEE970043210E /* NumericTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 275EC992265AEE970043210E /* NumericTextField.swift */; }; 275EC998265AF64E0043210E /* StatusMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 275EC997265AF64E0043210E /* StatusMessage.swift */; }; @@ -213,7 +212,6 @@ 2746C74426DF636900E31BD9 /* SensorPairingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorPairingService.swift; sourceTree = ""; }; 2746C74626DF63C800E31BD9 /* SensorPairing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorPairing.swift; sourceTree = ""; }; 274E71D2297ED77300FCFECD /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = ""; }; - 274E71D42986D4A600FCFECD /* CriticalAlarmsVolumeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CriticalAlarmsVolumeView.swift; sourceTree = ""; }; 275786AA26753CC400845D0E /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 275EC992265AEE970043210E /* NumericTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericTextField.swift; sourceTree = ""; }; 275EC997265AF64E0043210E /* StatusMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMessage.swift; sourceTree = ""; }; @@ -402,7 +400,6 @@ children = ( 277773B52639F51300431547 /* CustomDataPickerView.swift */, 276EF5E6264B1FCE00571021 /* AlarmSettingsView.swift */, - 274E71D42986D4A600FCFECD /* CriticalAlarmsVolumeView.swift */, ); path = AlarmSettings; sourceTree = ""; @@ -999,7 +996,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 274E71D52986D4A600FCFECD /* CriticalAlarmsVolumeView.swift in Sources */, 2746C74226DD0F8800E31BD9 /* Libre2DirectSetup.swift in Sources */, 27ED67BA26990D6B003E5DAB /* GenericObservableObject.swift in Sources */, 27850CFE25672C0C0020D109 /* HKUnit.swift in Sources */, diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift index 853d41b..307abb8 100644 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift @@ -317,6 +317,76 @@ struct OverrideDoNotDisturbRow: View { } } +struct CriticalAlertsBannerSection: View { + @Binding var criticalAlertsEnabled: Bool + @State private var presentableStatus: StatusMessage? + + var body: some View { + if NotificationHelperOverride.shouldOverrideRequestCriticalPermissions && !criticalAlertsEnabled { + Section { + VStack(alignment: .leading, spacing: 8) { + Label(LocalizedString("Critical Alerts", comment: "Title for the critical alerts banner"), systemImage: "bell.badge") + .font(.headline) + Text(LocalizedString("Enable critical alerts so glucose alarms can sound even when Do Not Disturb or silent mode is on.", comment: "Text describing the functionality of the 'Do Not Disturb' toggle")) + .font(.subheadline) + .foregroundColor(.secondary) + Button(action: requestCriticalAlerts) { + Text(LocalizedString("Enable Critical Alerts", comment: "Button text to request critical alert permissions")) + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + .tint(.orange) + .padding(.top, 4) + } + .padding(.vertical, 4) + } + .alert(item: $presentableStatus) { status in + Alert(title: Text(status.title), message: Text(status.message), dismissButton: .default(Text(LocalizedString("Got it!", comment: "Dismiss button for critical alerts permission alert")))) + } + } + } + + private func requestCriticalAlerts() { + NotificationHelper.requestCriticalAlertPermission { enabled in + criticalAlertsEnabled = enabled + if !enabled { + presentableStatus = StatusMessage( + title: LocalizedString("Could Not Enable Critical Alerts", comment: "Alert title when critical alert permission was not granted"), + message: LocalizedString("Critical alerts were not enabled. If you denied the permission prompt, you can enable them in iOS Settings > Notifications for this app. If this is a development build, also make sure the app has the critical alerts entitlement and that the provisioning profile supports it.", comment: "Alert message explaining how to enable critical alerts if permission was denied") + ) + } + } + } +} + +struct CriticalAlarmsVolumeSection: View { + private enum Key: String { + case mmCriticalAlarmsVolume = "com.loopkit.libreCriticalAlarmsVolume" + } + + @AppStorage(Key.mmCriticalAlarmsVolume.rawValue) var mmCriticalAlarmsVolume: Double = 60 + @State private var isEditing = false + + private var intVolume: Int { + Int(mmCriticalAlarmsVolume) + } + + var body: some View { + Section(header: Text(LocalizedString("Critical alarm volume", comment: "Header describing the volume of the critical alerts")), footer: Text(LocalizedString("Critical alarms will always be sent with volume at minimum 60%", comment: "Text describing that critical alerts are sent at a minimum volume of 60%"))) { + Slider( + value: $mmCriticalAlarmsVolume, + in: 60...100, + step: 5, + onEditingChanged: { editing in + isEditing = editing + } + ) + Text("\(intVolume)%") + .foregroundColor(isEditing ? .red : .blue) + } + } +} + struct AlarmSettingsView: View { private(set) var glucoseUnit: HKUnit diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift deleted file mode 100644 index 7eb08f0..0000000 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/CriticalAlarmsVolumeView.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// CriticalAlarmsVolumeView.swift -// LibreTransmitterUI -// -// Created by LoopKit Authors on 29/01/2023. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import SwiftUI -import LibreTransmitter - -struct CriticalAlertsBannerSection: View { - @Binding var criticalAlertsEnabled: Bool - @State private var presentableStatus: StatusMessage? - - var body: some View { - if NotificationHelperOverride.shouldOverrideRequestCriticalPermissions && !criticalAlertsEnabled { - Section { - VStack(alignment: .leading, spacing: 8) { - Label("Critical Alerts", systemImage: "bell.badge") - .font(.headline) - Text(LocalizedString("Enable critical alerts so glucose alarms can sound even when Do Not Disturb or silent mode is on.", comment: "Text describing the functionality of the 'Do Not Disturb' toggle")) - .font(.subheadline) - .foregroundColor(.secondary) - Button(action: requestCriticalAlerts) { - Text("Enable Critical Alerts") - .frame(maxWidth: .infinity) - } - .buttonStyle(.borderedProminent) - .tint(.orange) - .padding(.top, 4) - } - .padding(.vertical, 4) - } - .alert(item: $presentableStatus) { status in - Alert(title: Text(status.title), message: Text(status.message), dismissButton: .default(Text("Got it!"))) - } - } - } - - private func requestCriticalAlerts() { - NotificationHelper.requestCriticalAlertPermission { enabled in - criticalAlertsEnabled = enabled - if !enabled { - presentableStatus = StatusMessage( - title: "Could Not Enable Critical Alerts", - message: "Critical alerts were not enabled. If you denied the permission prompt, you can enable them in iOS Settings > Notifications for this app. If this is a development build, also make sure the app has the critical alerts entitlement and that the provisioning profile supports it." - ) - } - } - } -} - -struct CriticalAlarmsVolumeSection: View { - private enum Key: String { - case mmCriticalAlarmsVolume = "com.loopkit.libreCriticalAlarmsVolume" - } - - @AppStorage(Key.mmCriticalAlarmsVolume.rawValue) var mmCriticalAlarmsVolume: Double = 60 - @State private var isEditing = false - - private var intVolume: Int { - Int(mmCriticalAlarmsVolume) - } - - var body: some View { - Section(header: Text(LocalizedString("Critical alarm volume", comment: "Header describing the volume of the critical alerts")), footer: Text(LocalizedString("Critical alarms will always be sent with volume at minimum 60%", comment: "Text describing that critical alerts are sent at a minimum volume of 60%"))) { - Slider( - value: $mmCriticalAlarmsVolume, - in: 60...100, - step: 5, - onEditingChanged: { editing in - isEditing = editing - } - ) - Text("\(intVolume)%") - .foregroundColor(isEditing ? .red : .blue) - } - } -} From dfe5ec8e1479a8762a6539a10e202fc63db010dc Mon Sep 17 00:00:00 2001 From: ETolboom Date: Wed, 22 Apr 2026 11:36:56 +0200 Subject: [PATCH 6/6] Avoid CriticalAlarmsVolumeSection from not being rendered due to schedules changes not being detected Always show volume section if critical alerts permission been granted --- .../Views/Settings/AlarmSettings/AlarmSettingsView.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift index 307abb8..086f1f4 100644 --- a/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift +++ b/LibreTransmitterUI/Views/Settings/AlarmSettings/AlarmSettingsView.swift @@ -408,10 +408,6 @@ struct AlarmSettingsView: View { @State private var criticalAlertsEnabled = false - private var hasAnyScheduleWithOverride: Bool { - alarmState.schedules.contains { $0.overrideDoNotDisturb == true } - } - var body: some View { erasedWithKeyboardDismissal(list) .alert(item: $presentableStatus) { status in @@ -467,7 +463,7 @@ struct AlarmSettingsView: View { } - if criticalAlertsEnabled && hasAnyScheduleWithOverride { + if criticalAlertsEnabled { CriticalAlarmsVolumeSection() }