From fe469d915778bc16a28534e43f39c188a94e0b7e Mon Sep 17 00:00:00 2001 From: maxsoch Date: Tue, 7 Jul 2026 06:00:45 +0200 Subject: [PATCH] fix: explain notification permission state instead of failing silently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit requestAuthorization only shows the system prompt once ever; when permission is already denied it silently returns false and the checkbox just snapped off with no explanation. toggleNotify now checks the authorization status first: already-authorized enables directly, not-determined triggers the system prompt (errors surfaced in an alert), and denied shows an alert with an Open System Settings button pointing at the Notifications pane. Also adds a UNUserNotificationCenterDelegate so banners are shown even while BrewBar is the active app — otherwise macOS suppresses them, which reads as "notifications don't work" right after enabling. Co-Authored-By: Claude Fable 5 --- BrewBar/BrewBarApp.swift | 12 ++++++ BrewBar/SettingsWindowController.swift | 53 ++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/BrewBar/BrewBarApp.swift b/BrewBar/BrewBarApp.swift index 0499a28..73552b8 100644 --- a/BrewBar/BrewBarApp.swift +++ b/BrewBar/BrewBarApp.swift @@ -47,6 +47,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_: Notification) { Settings.registerDefaults() + UNUserNotificationCenter.current().delegate = self statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusItem.button?.imagePosition = .imageLeft // icon stays visible next to the count @@ -437,3 +438,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { logWindow?.update(text: logBuffer) } } + +extension AppDelegate: UNUserNotificationCenterDelegate { + /// Without this, macOS suppresses banners while BrewBar is the active + /// app (e.g. right after toggling the setting with the window open). + func userNotificationCenter(_: UNUserNotificationCenter, + willPresent _: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) + { + completionHandler([.banner]) + } +} diff --git a/BrewBar/SettingsWindowController.swift b/BrewBar/SettingsWindowController.swift index 5ab7ddc..f5ad924 100644 --- a/BrewBar/SettingsWindowController.swift +++ b/BrewBar/SettingsWindowController.swift @@ -246,15 +246,62 @@ class SettingsWindowController: NSWindowController { return } - UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, _ in + // requestAuthorization only shows the system prompt the first time + // ever; if permission was denied before, it silently returns false. + // Check the status first so the user learns what actually happened. + let center = UNUserNotificationCenter.current() + center.getNotificationSettings { notificationSettings in // callback arrives on a background queue; UI needs main DispatchQueue.main.async { - Settings.notifyOnNewUpdates = granted - sender.state = granted ? .on : .off + switch notificationSettings.authorizationStatus { + case .authorized, .provisional: + Settings.notifyOnNewUpdates = true + + case .notDetermined: + center.requestAuthorization(options: [.alert, .sound]) { granted, error in + DispatchQueue.main.async { + Settings.notifyOnNewUpdates = granted + sender.state = granted ? .on : .off + if let error { + self.showNotificationProblem(error.localizedDescription) + } + } + } + + case .denied: + sender.state = .off + self.showNotificationsDenied() + + @unknown default: + sender.state = .off + } } } } + func showNotificationsDenied() { + let alert = NSAlert() + alert.alertStyle = .warning + alert.messageText = "Notifications are disabled for BrewBar" + alert.informativeText = "macOS has notifications turned off for BrewBar. Enable them in System Settings > Notifications > BrewBar, then flip this switch again." + alert.addButton(withTitle: "Open System Settings") + alert.addButton(withTitle: "Cancel") + + if alert.runModal() == .alertFirstButtonReturn, + let url = URL(string: "x-apple.systempreferences:com.apple.Notifications-Settings.extension") + { + NSWorkspace.shared.open(url) + } + } + + func showNotificationProblem(_ message: String) { + let alert = NSAlert() + alert.alertStyle = .warning + alert.messageText = "Could not enable notifications" + alert.informativeText = message + alert.runModal() + } + @objc func toggleShowCount(_ sender: NSButton) { Settings.showCountInMenuBar = sender.state == .on appDelegate?.updateMenuBarCount() // reflect immediately, no refetch needed