LocalNotificationSender.swift 2.46 KB
import Foundation
import UIKit
import UserNotifications

enum LocalNotificationSenderError: LocalizedError {
    case notificationsDenied
    case notificationsUnsupportedStatus(Int)

    var errorDescription: String? {
        switch self {
        case .notificationsDenied:
            return "Notifications permission is denied."
        case .notificationsUnsupportedStatus(let status):
            return "Notifications permission status is unsupported: \(status)."
        }
    }
}

final class LocalNotificationSender {
    static let shared = LocalNotificationSender()

    private let center: UNUserNotificationCenter

    init(center: UNUserNotificationCenter = .current()) {
        self.center = center
    }

    func send(
        title: String,
        body: String,
        link: String,
        onlyWhenAppNotActive: Bool = true
    ) async throws -> Bool {
        if onlyWhenAppNotActive, UIApplication.shared.applicationState == .active {
            return false
        }

        try await ensureNotificationPermission()

        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default
        content.userInfo = notificationUserInfo(link: link)

        let request = UNNotificationRequest(
            identifier: "doublefeel.local.\(UUID().uuidString)",
            content: content,
            trigger: UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
        )
        try await center.add(request)
        return true
    }

    private func ensureNotificationPermission() async throws {
        let settings = await center.notificationSettings()
        switch settings.authorizationStatus {
        case .authorized, .provisional, .ephemeral:
            return
        case .notDetermined:
            let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge])
            if !granted {
                throw LocalNotificationSenderError.notificationsDenied
            }
        case .denied:
            throw LocalNotificationSenderError.notificationsDenied
        @unknown default:
            throw LocalNotificationSenderError.notificationsUnsupportedStatus(
                settings.authorizationStatus.rawValue
            )
        }
    }

    private func notificationUserInfo(link: String) -> [String: Any] {
        guard !link.isEmpty else { return [:] }
        return [
            "url": link,
            "link": link,
        ]
    }
}