111 lines
3.8 KiB
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()
|
|
}
|
|
}
|