diff --git a/BrewBar/BrewBarApp.swift b/BrewBar/BrewBarApp.swift index 81c3f89..7fc6ab6 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 NotificationCenter.default.addObserver( self, @@ -465,3 +466,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { presentWindow(of: logWindow) } } + +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