bugger/Sources/Services/NotificationService.swift

111 lines
3.8 KiB
Swift

import AppKit
import Foundation
import UserNotifications
final class NotificationService: NSObject, UNUserNotificationCenterDelegate {
static let shared = NotificationService()
private let center = UNUserNotificationCenter.current()
private var isAuthorized = false
private override init() {
super.init()
center.delegate = self
}
func requestAuthorizationIfNeeded() async {
guard !isAuthorized else { return }
let granted = (try? await center.requestAuthorization(options: [.alert, .sound, .badge])) ?? false
isAuthorized = granted
}
func handleChanges(_ changes: [BugChange]) async {
await requestAuthorizationIfNeeded()
guard isAuthorized else { return }
let significant = changes
.filter { change in
guard change.type == .newBug || change.type == .priorityChanged else { return false }
// Skip activity toasts for bugs that are already done.
return change.bug.status != .resolved && change.bug.status != .closed
}
.prefix(3)
for change in significant {
deliver(change)
}
let statusChanges = changes.filter { $0.type == .statusChanged }
if statusChanges.count > 1 {
deliverBatchStatusChange(statusChanges)
} else if let single = statusChanges.first {
deliver(single)
}
}
private func deliver(_ change: BugChange) {
let content = UNMutableNotificationContent()
switch change.type {
case .newBug:
content.title = "New Bug Assigned"
content.body = "[\(change.bug.priority.rawValue)] \(change.bug.title)"
content.sound = .default
case .statusChanged:
content.title = "Bug Status Changed"
content.body = "\(change.bug.title)\(change.bug.status.rawValue)"
case .priorityChanged:
content.title = "Bug Priority Changed"
content.body = "\(change.bug.title)\(change.bug.priority.rawValue)"
case .assigneeChanged:
return
}
var userInfo: [String: Any] = ["bugId": change.bug.id]
if let url = change.bug.feishuURL {
userInfo["feishuURL"] = url.absoluteString
}
content.userInfo = userInfo
let request = UNNotificationRequest(
identifier: "bugger-\(change.bug.id)-\(Date().timeIntervalSince1970)",
content: content,
trigger: nil
)
center.add(request)
}
private func deliverBatchStatusChange(_ changes: [BugChange]) {
let content = UNMutableNotificationContent()
content.title = "\(changes.count) Bugs Updated"
content.body = changes.prefix(3).map { "\($0.bug.title)" }.joined(separator: "\n")
content.sound = .default
let request = UNNotificationRequest(
identifier: "bugger-batch-\(Date().timeIntervalSince1970)",
content: content,
trigger: nil
)
center.add(request)
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
completionHandler([.banner, .sound])
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
if let urlString = response.notification.request.content.userInfo["feishuURL"] as? String,
let url = URL(string: urlString) {
NSWorkspace.shared.open(url)
}
completionHandler()
}
}