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:
+93
@@ -0,0 +1,93 @@
|
||||
package com.tt.honeyDue.notifications
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
||||
/**
|
||||
* Android NotificationChannels that map to the iOS UNNotificationCategory
|
||||
* identifiers defined in `iosApp/iosApp/PushNotifications/NotificationCategories.swift`.
|
||||
*
|
||||
* iOS uses categories + per-notification actions. Android uses channels for
|
||||
* importance grouping. Channels here mirror the four high-level iOS tones:
|
||||
*
|
||||
* task_reminder — default importance (upcoming/due-soon reminders)
|
||||
* task_overdue — high importance (user is late, needs attention)
|
||||
* residence_invite — default importance (social-style invite, not urgent)
|
||||
* subscription — low importance (billing/status changes, passive info)
|
||||
*
|
||||
* User-visible names and descriptions match the keys in
|
||||
* `composeApp/src/commonMain/composeResources/values/strings.xml`
|
||||
* (`notif_channel_*_name`, `notif_channel_*_description`).
|
||||
*/
|
||||
object NotificationChannels {
|
||||
|
||||
const val TASK_REMINDER: String = "task_reminder"
|
||||
const val TASK_OVERDUE: String = "task_overdue"
|
||||
const val RESIDENCE_INVITE: String = "residence_invite"
|
||||
const val SUBSCRIPTION: String = "subscription"
|
||||
|
||||
// English fallback strings. These are duplicated in composeResources
|
||||
// strings.xml under the matching notif_channel_* keys so localised builds
|
||||
// can override them. Services without access to Compose resources fall
|
||||
// back to these values.
|
||||
private const val NAME_TASK_REMINDER = "Task Reminders"
|
||||
private const val NAME_TASK_OVERDUE = "Overdue Tasks"
|
||||
private const val NAME_RESIDENCE_INVITE = "Residence Invites"
|
||||
private const val NAME_SUBSCRIPTION = "Subscription Updates"
|
||||
|
||||
private const val DESC_TASK_REMINDER = "Upcoming and due-soon task reminders"
|
||||
private const val DESC_TASK_OVERDUE = "Alerts when a task is past its due date"
|
||||
private const val DESC_RESIDENCE_INVITE = "Invitations to join a shared residence"
|
||||
private const val DESC_SUBSCRIPTION = "Subscription status and billing updates"
|
||||
|
||||
/**
|
||||
* Create all four channels if they don't already exist. Safe to call
|
||||
* repeatedly — `NotificationManagerCompat.createNotificationChannel` is
|
||||
* a no-op when a channel with the same id already exists.
|
||||
*/
|
||||
fun ensureChannels(context: Context) {
|
||||
// Channels only exist on O+; on older versions this is a no-op and the
|
||||
// NotificationCompat layer ignores channel ids.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
val compat = NotificationManagerCompat.from(context)
|
||||
val channels = listOf(
|
||||
NotificationChannel(
|
||||
TASK_REMINDER,
|
||||
NAME_TASK_REMINDER,
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
).apply { description = DESC_TASK_REMINDER },
|
||||
NotificationChannel(
|
||||
TASK_OVERDUE,
|
||||
NAME_TASK_OVERDUE,
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply { description = DESC_TASK_OVERDUE },
|
||||
NotificationChannel(
|
||||
RESIDENCE_INVITE,
|
||||
NAME_RESIDENCE_INVITE,
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
).apply { description = DESC_RESIDENCE_INVITE },
|
||||
NotificationChannel(
|
||||
SUBSCRIPTION,
|
||||
NAME_SUBSCRIPTION,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply { description = DESC_SUBSCRIPTION }
|
||||
)
|
||||
channels.forEach { compat.createNotificationChannel(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a [NotificationPayload.type] string to a channel id. Unknown types
|
||||
* fall back to [TASK_REMINDER] (default importance, safe default).
|
||||
*/
|
||||
fun channelIdForType(type: String): String = when (type) {
|
||||
TASK_OVERDUE -> TASK_OVERDUE
|
||||
RESIDENCE_INVITE -> RESIDENCE_INVITE
|
||||
SUBSCRIPTION -> SUBSCRIPTION
|
||||
TASK_REMINDER -> TASK_REMINDER
|
||||
else -> TASK_REMINDER
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user