From 4f8772f9868f6c97ccad1242ff7b355cdb8d5795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Sun, 18 Jan 2026 00:23:38 +0000 Subject: [PATCH] feat: Add notification types to enable opening specific URLs upon interaction. --- .../Utilities/NotificationManager.swift | 71 +++++++++++++++++-- MacTorn/MacTorn/ViewModels/AppState.swift | 37 ++++++---- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/MacTorn/MacTorn/Utilities/NotificationManager.swift b/MacTorn/MacTorn/Utilities/NotificationManager.swift index ae131bf..34cc09e 100644 --- a/MacTorn/MacTorn/Utilities/NotificationManager.swift +++ b/MacTorn/MacTorn/Utilities/NotificationManager.swift @@ -1,10 +1,46 @@ import Foundation import UserNotifications +import AppKit -class NotificationManager { +enum NotificationType: String { + case drugReady + case medicalReady + case boosterReady + case landed + case chainExpiring + case released + case energy + case nerve + case happy + case life + + var url: URL { + switch self { + case .drugReady, .medicalReady, .boosterReady: + return URL(string: "https://www.torn.com/item.php")! + case .landed: + return URL(string: "https://www.torn.com/page.php?sid=ItemMarket")! + case .chainExpiring: + return URL(string: "https://www.torn.com/factions.php?step=your#/tab=wars")! + case .released: + return URL(string: "https://www.torn.com/")! + case .energy, .happy: + return URL(string: "https://www.torn.com/gym.php")! + case .nerve: + return URL(string: "https://www.torn.com/crimes.php")! + case .life: + return URL(string: "https://www.torn.com/hospitalview.php")! + } + } +} + +class NotificationManager: NSObject, UNUserNotificationCenterDelegate { static let shared = NotificationManager() - - private init() {} + + private override init() { + super.init() + UNUserNotificationCenter.current().delegate = self + } func requestPermission() async { do { @@ -18,22 +54,45 @@ class NotificationManager { } } - func send(title: String, body: String) { + func send(title: String, body: String, type: NotificationType) { let content = UNMutableNotificationContent() content.title = title content.body = body content.sound = .default - + content.categoryIdentifier = type.rawValue + let request = UNNotificationRequest( identifier: UUID().uuidString, content: content, trigger: nil // Immediate ) - + UNUserNotificationCenter.current().add(request) { error in if let error = error { print("Notification error: \(error)") } } } + + // MARK: - UNUserNotificationCenterDelegate + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + let categoryIdentifier = response.notification.request.content.categoryIdentifier + if let type = NotificationType(rawValue: categoryIdentifier) { + NSWorkspace.shared.open(type.url) + } + completionHandler() + } + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + completionHandler([.banner, .sound]) + } } diff --git a/MacTorn/MacTorn/ViewModels/AppState.swift b/MacTorn/MacTorn/ViewModels/AppState.swift index 3b803c3..fe6a490 100644 --- a/MacTorn/MacTorn/ViewModels/AppState.swift +++ b/MacTorn/MacTorn/ViewModels/AppState.swift @@ -501,31 +501,31 @@ class AppState: ObservableObject { if let prevCD = previousCooldowns, let currentCD = newData.cooldowns { if prevCD.drug > 0 && currentCD.drug == 0 { - NotificationManager.shared.send(title: "Drug Ready! 💊", body: "Drug cooldown has ended") + NotificationManager.shared.send(title: "Drug Ready! 💊", body: "Drug cooldown has ended", type: .drugReady) } if prevCD.medical > 0 && currentCD.medical == 0 { - NotificationManager.shared.send(title: "Medical Ready! 🏥", body: "Medical cooldown has ended") + NotificationManager.shared.send(title: "Medical Ready! 🏥", body: "Medical cooldown has ended", type: .medicalReady) } if prevCD.booster > 0 && currentCD.booster == 0 { - NotificationManager.shared.send(title: "Booster Ready! 🚀", body: "Booster cooldown has ended") + NotificationManager.shared.send(title: "Booster Ready! 🚀", body: "Booster cooldown has ended", type: .boosterReady) } } if let prevTravel = previousTravel, let currentTravel = newData.travel { if prevTravel.isTraveling && !currentTravel.isTraveling { - NotificationManager.shared.send(title: "Landed! ✈️", body: "You have arrived in \(currentTravel.destination ?? "destination")") + NotificationManager.shared.send(title: "Landed! ✈️", body: "You have arrived in \(currentTravel.destination ?? "destination")", type: .landed) } } if let chain = newData.chain, chain.isActive { if chain.timeoutRemaining < 60 && chain.timeoutRemaining > 0 { - NotificationManager.shared.send(title: "Chain Expiring! ⚠️", body: "Chain timeout in \(chain.timeoutRemaining) seconds!") + NotificationManager.shared.send(title: "Chain Expiring! ⚠️", body: "Chain timeout in \(chain.timeoutRemaining) seconds!", type: .chainExpiring) } } if let prevStatus = previousStatus, let currentStatus = newData.status { if !prevStatus.isOkay && currentStatus.isOkay { - NotificationManager.shared.send(title: "Released! 🎉", body: "You are now free") + NotificationManager.shared.send(title: "Released! 🎉", body: "You are now free", type: .released) } } } @@ -533,20 +533,29 @@ class AppState: ObservableObject { private func checkBarNotification(prevBar: Bar, currentBar: Bar, barType: NotificationRule.BarType) { let prevPct = prevBar.percentage let currentPct = currentBar.percentage - + for rule in notificationRules where rule.enabled && rule.barType == barType { let threshold = Double(rule.threshold) - + if prevPct < threshold && currentPct >= threshold { let title: String + let notificationType: NotificationType switch barType { - case .energy: title = "Energy \(rule.threshold)%! ⚡️" - case .nerve: title = "Nerve \(rule.threshold)%! 💪" - case .happy: title = "Happy \(rule.threshold)%! 😊" - case .life: title = "Life \(rule.threshold)%! ❤️" + case .energy: + title = "Energy \(rule.threshold)%! ⚡️" + notificationType = .energy + case .nerve: + title = "Nerve \(rule.threshold)%! 💪" + notificationType = .nerve + case .happy: + title = "Happy \(rule.threshold)%! 😊" + notificationType = .happy + case .life: + title = "Life \(rule.threshold)%! ❤️" + notificationType = .life } - NotificationManager.shared.send(title: title, body: "\(barType.rawValue) is now at \(currentBar.current)/\(currentBar.maximum)") - + NotificationManager.shared.send(title: title, body: "\(barType.rawValue) is now at \(currentBar.current)/\(currentBar.maximum)", type: notificationType) + if let sound = NotificationSound(rawValue: rule.soundName) { SoundManager.shared.play(sound) }