mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 11:01:07 +00:00
135 lines
5.9 KiB
Swift
135 lines
5.9 KiB
Swift
//
|
|
// MirroredNotificationSupport.swift
|
|
// QuickDrop
|
|
//
|
|
|
|
import Foundation
|
|
import CryptoKit
|
|
import UserNotifications
|
|
import LUI
|
|
|
|
final class MirroredNotificationPresenter {
|
|
|
|
static let shared = MirroredNotificationPresenter()
|
|
|
|
private let center = UNUserNotificationCenter.current()
|
|
private let queue = DispatchQueue(label: "MirroredNotificationPresenter")
|
|
private var pendingNotifications: [(Sharing_Nearby_MirroredNotificationMetadata, String?)] = []
|
|
private var authorizationRequestInFlight = false
|
|
private init() {}
|
|
|
|
func present(metadata: Sharing_Nearby_MirroredNotificationMetadata, senderDeviceName: String?) {
|
|
queue.async {
|
|
self.postNotificationIfAuthorized(metadata: metadata, senderDeviceName: senderDeviceName)
|
|
}
|
|
}
|
|
|
|
private func postNotificationIfAuthorized(metadata: Sharing_Nearby_MirroredNotificationMetadata, senderDeviceName: String?) {
|
|
center.getNotificationSettings { settings in
|
|
self.queue.async {
|
|
self.handleAuthorizationStatus(
|
|
settings.authorizationStatus,
|
|
metadata: metadata,
|
|
senderDeviceName: senderDeviceName
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func handleAuthorizationStatus(
|
|
_ status: UNAuthorizationStatus,
|
|
metadata: Sharing_Nearby_MirroredNotificationMetadata,
|
|
senderDeviceName: String?
|
|
) {
|
|
switch status {
|
|
case .authorized, .provisional, .ephemeral:
|
|
post(metadata: metadata, senderDeviceName: senderDeviceName)
|
|
case .notDetermined:
|
|
pendingNotifications.append((metadata, senderDeviceName))
|
|
requestAuthorizationIfNeeded()
|
|
case .denied:
|
|
log("[MirroredNotificationPresenter] Notifications are denied in system settings; cannot mirror.")
|
|
@unknown default:
|
|
log("[MirroredNotificationPresenter] Unknown notification authorization status; skipping mirrored notification.")
|
|
}
|
|
}
|
|
|
|
private func requestAuthorizationIfNeeded() {
|
|
if authorizationRequestInFlight {
|
|
return
|
|
}
|
|
|
|
authorizationRequestInFlight = true
|
|
|
|
DispatchQueue.main.async {
|
|
self.center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
|
self.queue.async {
|
|
self.authorizationRequestInFlight = false
|
|
|
|
if let error = error {
|
|
log("[MirroredNotificationPresenter] Authorization request failed: \(error.localizedDescription)")
|
|
}
|
|
|
|
guard granted else {
|
|
log("[MirroredNotificationPresenter] Authorization request not granted; dropping queued mirrored notifications.")
|
|
self.pendingNotifications.removeAll()
|
|
return
|
|
}
|
|
|
|
let queued = self.pendingNotifications
|
|
self.pendingNotifications.removeAll()
|
|
for (queuedMetadata, queuedSender) in queued {
|
|
self.post(metadata: queuedMetadata, senderDeviceName: queuedSender)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func post(metadata: Sharing_Nearby_MirroredNotificationMetadata, senderDeviceName: String?) {
|
|
let senderName = senderDeviceName?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let hasSenderName = senderName?.isEmpty == false
|
|
let displaySender = hasSenderName ? senderName! : "NotificationSyncFallbackSenderAndroid".localized()
|
|
let identifierSender = hasSenderName ? senderName! : "android"
|
|
let cleanAppName = metadata.appName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let cleanTitle = metadata.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let cleanBody = metadata.body.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let notificationKey = metadata.notificationKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
let content = UNMutableNotificationContent()
|
|
content.title = cleanTitle.isEmpty ? (cleanAppName.isEmpty ? "NotificationSyncFallbackTitle".localized() : cleanAppName) : cleanTitle
|
|
content.body = cleanBody
|
|
content.subtitle = cleanAppName.isEmpty ? displaySender : "\(cleanAppName) · \(displaySender)"
|
|
content.sound = .default
|
|
content.threadIdentifier = "quickdrop-mirror-\(sanitizeIdentifierComponent(identifierSender))"
|
|
|
|
let identifier = makeIdentifier(sender: identifierSender, notificationKey: notificationKey)
|
|
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
|
center.add(request) { error in
|
|
if let error = error {
|
|
log("[MirroredNotificationPresenter] Could not post notification: \(error.localizedDescription)")
|
|
} else {
|
|
DispatchQueue.main.async {
|
|
Settings.sharedInstance.incomingNotificationCount += 1
|
|
let count = Settings.sharedInstance.incomingNotificationCount
|
|
log("[MirroredNotificationPresenter] Posted mirrored notification (\(identifier)). Current count: \(count)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func makeIdentifier(sender: String, notificationKey: String) -> String {
|
|
let raw = "\(sender)|\(notificationKey)"
|
|
let digest = SHA256.hash(data: Data(raw.utf8))
|
|
let prefix = digest.prefix(12).map { String(format: "%02x", $0) }.joined()
|
|
return "quickdrop-mirror-\(prefix)"
|
|
}
|
|
|
|
private func sanitizeIdentifierComponent(_ value: String) -> String {
|
|
let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_"))
|
|
let scalarView = value.unicodeScalars.map { allowed.contains($0) ? Character($0) : "_" }
|
|
let sanitized = String(scalarView)
|
|
return sanitized.isEmpty ? "android" : sanitized
|
|
}
|
|
}
|