90 lines
3.0 KiB
Swift
90 lines
3.0 KiB
Swift
// Sources/Core/Push/NotificationDelegate.swift
|
|
// T-2.9: APNs — foreground presentation control + tap handling
|
|
|
|
import UserNotifications
|
|
import UIKit
|
|
|
|
/// Centralised `UNUserNotificationCenterDelegate`.
|
|
///
|
|
/// Responsibilities:
|
|
/// - Suppresses the banner when the user is already viewing the relevant
|
|
/// terminal session (`visibleSessionId`).
|
|
/// - Posts `"piRemote.openSession"` when the user taps a notification,
|
|
/// carrying the session-id as the notification object, so any subscriber
|
|
/// (e.g. the root navigation stack) can navigate to the right session.
|
|
@MainActor
|
|
final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
|
|
|
// MARK: - Singleton
|
|
|
|
static let shared = NotificationDelegate()
|
|
|
|
private override init() {}
|
|
|
|
// MARK: - State
|
|
|
|
/// Set by the visible terminal view so foreground banners can be suppressed.
|
|
var visibleSessionId: String? = nil
|
|
|
|
// MARK: - Setup
|
|
|
|
/// Wire this delegate into `UNUserNotificationCenter`.
|
|
/// Call once from `piRemoteApp.onAppear`.
|
|
func setup() {
|
|
UNUserNotificationCenter.current().delegate = self
|
|
}
|
|
|
|
// MARK: - Permission
|
|
|
|
/// Request alert + sound + badge authorisation.
|
|
/// - Returns: `true` if the user granted permission.
|
|
func requestPermission() async -> Bool {
|
|
do {
|
|
return try await UNUserNotificationCenter.current()
|
|
.requestAuthorization(options: [.alert, .sound, .badge])
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// MARK: - UNUserNotificationCenterDelegate
|
|
|
|
/// Foreground presentation: suppress the banner when the relevant session
|
|
/// is already on-screen.
|
|
nonisolated func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
willPresent notification: UNNotification,
|
|
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
|
|
) {
|
|
let sessionId = notification.request.content.userInfo["sessionId"] as? String
|
|
|
|
Task { @MainActor in
|
|
if let sessionId, sessionId == self.visibleSessionId {
|
|
// Session is already visible — suppress the banner entirely.
|
|
completionHandler([])
|
|
} else {
|
|
// Show banner + sound + badge update.
|
|
completionHandler([.banner, .sound, .badge])
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Tap handling: post `"piRemote.openSession"` so any subscriber can
|
|
/// navigate to the correct session without a tight coupling.
|
|
nonisolated func userNotificationCenter(
|
|
_ center: UNUserNotificationCenter,
|
|
didReceive response: UNNotificationResponse,
|
|
withCompletionHandler completionHandler: @escaping () -> Void
|
|
) {
|
|
let sessionId = response.notification.request.content.userInfo["sessionId"] as? String
|
|
|
|
Task { @MainActor in
|
|
NotificationCenter.default.post(
|
|
name: Notification.Name("piRemote.openSession"),
|
|
object: sessionId
|
|
)
|
|
completionHandler()
|
|
}
|
|
}
|
|
}
|