Nucleus
OS integration

Notifications sur macOS

Bindings Kotlin du framework UserNotifications d'Apple couvrant l'autorisation, les catégories, les pièces jointes, la saisie texte, les planifications et les niveaux d'interruption.

Le module notification-macos vous permet de piloter le framework UserNotifications d'Apple depuis Kotlin. Vous demandez l'autorisation, planifiez des notifications, enregistrez des catégories d'actions avec saisie texte, ajoutez des pièces jointes et traitez les réponses via NotificationCenter et un délégué typé.

Ajouter la dépendance

build.gradle.kts
dependencies {
    implementation("dev.nucleusframework:nucleus.notification-macos:2.0.7")
}

Envoyer une notification

Demandez l'autorisation, puis soumettez une NotificationRequest avec NotificationCenter.add :

import dev.nucleusframework.notification.*

NotificationCenter.requestAuthorization(
    setOf(AuthorizationOption.ALERT, AuthorizationOption.SOUND, AuthorizationOption.BADGE),
) { granted, error ->
    if (!granted) return@requestAuthorization

    NotificationCenter.add(
        NotificationRequest(
            identifier = "greeting",
            content = NotificationContent(
                title = "Hello",
                body = "Bienvenue dans Nucleus",
                sound = NotificationSound.Default,
                interruptionLevel = InterruptionLevel.TIME_SENSITIVE,
            ),
            trigger = NotificationTrigger.TimeInterval(interval = 5.0),
        ),
    )
}

Pour une livraison immédiate, laissez trigger non renseigné (sa valeur par défaut est null).

macOS ne délivre les notifications que depuis une app bundlée dotée d'un bundle identifier. En développement, lancez ./gradlew runDistributable ou ./gradlew runGraalvmNative — un simple ./gradlew run produit un process JVM non bundlé que le système ignore silencieusement.

Fonctionnement

Chaque API est un wrapper typé sur l'appel UserNotifications correspondant, atteint via un pont JNI. NotificationCenter est un singleton qui transmet les callbacks UNUserNotificationCenterDelegate au NotificationCenterDelegate que vous enregistrez. Sur les plateformes autres que macOS, chaque méthode est un no-op ; testez d'abord NotificationCenter.isAvailable.

Les catégories vous permettent d'enregistrer en amont des jeux de boutons d'action réutilisables — y compris des actions de saisie texte — puis d'y faire référence par identifiant depuis un NotificationContent. Le système stocke le jeu de catégories, donc chaque requête ne porte que la chaîne categoryIdentifier.

Les callbacks du délégué ne sont pas dispatchés sur les threads AWT ou Swing. Repassez sur le dispatcher UI actif avant de toucher à l'état de l'interface depuis willPresent ou didReceive.

Référence de l'API

Vérifier la disponibilité

if (NotificationCenter.isAvailable) {
    // exécution sur macOS avec pont natif chargé et app bundlée
}

Demander l'autorisation

requestAuthorization prend un ensemble de valeurs AuthorizationOption et un callback qui rapporte le résultat :

NotificationCenter.requestAuthorization(
    setOf(AuthorizationOption.ALERT, AuthorizationOption.SOUND, AuthorizationOption.BADGE),
) { granted, error -> /* … */ }

Options disponibles : BADGE, SOUND, ALERT, CRITICAL_ALERT, PROVIDES_APP_NOTIFICATION_SETTINGS, PROVISIONAL, TIME_SENSITIVE.

Enregistrer des catégories avec boutons d'action

Construisez une NotificationCategory à partir d'entrées NotificationAction ou TextInputNotificationAction, puis enregistrez l'ensemble complet avec setNotificationCategories :

val category = NotificationCategory(
    identifier = "MESSAGE",
    actions = listOf(
        TextInputNotificationAction(
            identifier = "REPLY",
            title = "Répondre",
            textInputButtonTitle = "Envoyer",
            textInputPlaceholder = "Tapez une réponse…",
        ),
    ),
    options = setOf(CategoryOption.CUSTOM_DISMISS_ACTION),
)
NotificationCenter.setNotificationCategories(setOf(category))

Référez-vous à la catégorie depuis une notification via NotificationContent.categoryIdentifier.

Ajouter des pièces jointes

NotificationAttachment.url est une chaîne de chemin de fichier, pas un java.net.URI :

val request = NotificationRequest(
    identifier = "screenshot",
    content = NotificationContent(
        title = "Capture enregistrée",
        body = "Cliquez pour prévisualiser",
        attachments = listOf(
            NotificationAttachment(identifier = "img", url = imageFile.absolutePath),
        ),
    ),
)

Planifier avec des déclencheurs

DéclencheurCas d'usage
trigger = null (défaut)maintenant
NotificationTrigger.TimeInterval(interval, repeats)différé ou récurrent (la récurrence exige interval >= 60)
NotificationTrigger.Calendar(dateComponents, repeats)"tous les lundis à 9h" via DateComponents

Traiter les réponses avec un délégué

Implémentez NotificationCenterDelegate pour contrôler la présentation au premier plan et réagir aux taps, sélections d'action et réponses de saisie texte :

NotificationCenter.setDelegate(object : NotificationCenterDelegate {
    override fun willPresent(notification: DeliveredNotification): Set<PresentationOption> =
        setOf(PresentationOption.BANNER, PresentationOption.SOUND)

    override fun didReceive(response: NotificationResponse) { /* … */ }
})

willPresent retourne les options de présentation à utiliser tant que l'app est au premier plan ; retourne un ensemble vide pour traiter la notification silencieusement. NotificationResponse.userText contient le texte saisi par l'utilisateur dans une TextInputNotificationAction.

Inspecter et supprimer les notifications

getPendingNotifications et getDeliveredNotifications prennent des callbacks et rapportent ce qui est en file ou affiché. Supprimez des entrées avec removePendingNotifications(ids) / removeAllPendingNotifications() et removeDeliveredNotifications(ids) / removeAllDeliveredNotifications().

Notes

  • Les apps sandboxées ont besoin de l'entitlement notifications. Faites pointer la propriété entitlementsFile macOS du DSL Gradle Nucleus vers un fichier .entitlements qui le déclare.
  • Pour les alertes qui passent outre les modes Focus, mettez interruptionLevel = InterruptionLevel.CRITICAL et demandez AuthorizationOption.CRITICAL_ALERT.
  • Un bundle identifier qui ne correspond pas est la cause numéro un d'échec silencieux — vérifiez que NucleusApp.appId correspond au bundle id de l'app packagée.

Et ensuite