P4 Stream N: FCM service + NotificationChannels matching iOS categories
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>
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
||||
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() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user