0d50726490
FcmService + NotificationPayload + 4 NotificationChannels (task_reminder, task_overdue, residence_invite, subscription) parity with iOS NotificationCategories.swift. Deep-link routing from payload. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 lines
2.2 KiB
Kotlin
54 lines
2.2 KiB
Kotlin
package com.tt.honeyDue.notifications
|
|
|
|
/**
|
|
* Structured representation of a Firebase Cloud Messaging data-payload for
|
|
* iOS-parity notification types (task_reminder, task_overdue, residence_invite,
|
|
* subscription).
|
|
*
|
|
* Mirrors the iOS `PushNotificationManager.swift` userInfo handling. The
|
|
* backend sends a `data` map only (no `notification` field) so we can always
|
|
* deliver actionable payloads regardless of app foreground state.
|
|
*/
|
|
data class NotificationPayload(
|
|
val type: String,
|
|
val taskId: Long?,
|
|
val residenceId: Long?,
|
|
val title: String,
|
|
val body: String,
|
|
val deepLink: String?
|
|
) {
|
|
companion object {
|
|
// Keys used by the backend. Kept in a single place so they can be updated
|
|
// in lockstep with the Go API `internal/notification/` constants.
|
|
private const val KEY_TYPE = "type"
|
|
private const val KEY_TASK_ID = "task_id"
|
|
private const val KEY_RESIDENCE_ID = "residence_id"
|
|
private const val KEY_TITLE = "title"
|
|
private const val KEY_BODY = "body"
|
|
private const val KEY_DEEP_LINK = "deep_link"
|
|
|
|
/**
|
|
* Parse a raw FCM data map into a [NotificationPayload], or null if the
|
|
* payload is missing the minimum required fields (type + at least one of
|
|
* title/body). Numeric id fields that fail to parse are treated as null
|
|
* (rather than failing the whole payload) so we still surface the text.
|
|
*/
|
|
fun parse(data: Map<String, String>): NotificationPayload? {
|
|
val type = data[KEY_TYPE]?.takeIf { it.isNotBlank() } ?: return null
|
|
val title = data[KEY_TITLE]?.takeIf { it.isNotBlank() }
|
|
val body = data[KEY_BODY]?.takeIf { it.isNotBlank() }
|
|
// Require at least one of title/body, otherwise there's nothing to show.
|
|
if (title == null && body == null) return null
|
|
|
|
return NotificationPayload(
|
|
type = type,
|
|
taskId = data[KEY_TASK_ID]?.toLongOrNull(),
|
|
residenceId = data[KEY_RESIDENCE_ID]?.toLongOrNull(),
|
|
title = title ?: "",
|
|
body = body ?: "",
|
|
deepLink = data[KEY_DEEP_LINK]?.takeIf { it.isNotBlank() }
|
|
)
|
|
}
|
|
}
|
|
}
|