world-manager/World Manager for Minecraft/Services/ScanNotificationService.swift

85 lines
2.4 KiB
Swift

//
// ScanNotificationService.swift
// World Manager for Minecraft
//
// Created by Codex on 2026-05-27.
//
import AppKit
import Foundation
import UserNotifications
@MainActor
protocol ScanNotificationServicing: AnyObject {
func requestAuthorizationIfNeeded() async
func notifyScanCompleted(for source: MinecraftSource, duration: TimeInterval) async
}
@MainActor
final class ScanNotificationService: NSObject, ScanNotificationServicing {
static let shared = ScanNotificationService()
static let longScanThreshold: TimeInterval = 8
private let center: UNUserNotificationCenter
init(center: UNUserNotificationCenter = .current()) {
self.center = center
super.init()
}
func requestAuthorizationIfNeeded() async {
let settings = await center.notificationSettings()
guard settings.authorizationStatus == .notDetermined else {
return
}
do {
_ = try await center.requestAuthorization(options: [.alert, .sound])
} catch {
return
}
}
func notifyScanCompleted(for source: MinecraftSource, duration: TimeInterval) async {
guard shouldNotifyAboutCompletedScan(duration: duration, isAppActive: NSApp.isActive) else {
return
}
let settings = await center.notificationSettings()
guard settings.authorizationStatus == .authorized else {
return
}
let content = UNMutableNotificationContent()
content.title = "Scan complete"
content.subtitle = source.displayName
content.body = Self.completionMessage(itemCount: source.indexedItemCount)
content.sound = .default
let identifier = "scan-complete-\(source.id.absoluteString)-\(UUID().uuidString)"
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
do {
try await center.add(request)
} catch {
return
}
}
func shouldNotifyAboutCompletedScan(duration: TimeInterval, isAppActive: Bool) -> Bool {
duration >= Self.longScanThreshold && !isAppActive
}
nonisolated static func completionMessage(itemCount: Int) -> String {
switch itemCount {
case 0:
return "No worlds or packs were found."
case 1:
return "Found 1 item."
default:
return "Found \(itemCount) items."
}
}
}