Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 70 additions & 13 deletions android/src/main/java/app/tauri/notification/UnifiedPushNotifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
Expand All @@ -13,43 +14,99 @@ object UnifiedPushNotifier {
private const val CHANNEL_ID = "messages"

fun showFromPush(context: Context, rawMessage: String) {
val notification = try {
JSONObject(rawMessage).optJSONObject("notification")
val rootJson = try {
JSONObject(rawMessage)
} catch (e: Exception) {
null
} ?: return

val notification = rootJson.optJSONObject("notification") ?: return

val roomId = notification.optString("room_id")
val eventId = notification.optString("event_id")
val sender = notification.optString("sender_display_name")
val title = notification.optString("room_name").ifEmpty { "New message" }
val body = buildBody(notification, sender)

// Extract user_id from top-level or notification sub-object
val userId = rootJson.optString("user_id").ifEmpty {
notification.optString("user_id")
}

ensureChannel(context)

val iconId = context.resources
.getIdentifier("notification_icon", "drawable", context.packageName)
.takeIf { it != 0 } ?: android.R.drawable.ic_dialog_info

val notifId = (roomId.ifEmpty { eventId }).hashCode()

// Build intent mimicking TauriNotificationManager.buildIntent() so
// that NotificationPlugin.onIntent() -> handleNotificationActionPerformed()
// -> extractLocalNotificationData() extracts room_id/event_id/user_id
// from NOTIFICATION_OBJ_INTENT_KEY, matching the warm-path behavior.
val intent = buildPushIntent(context, notifId, roomId, eventId, userId)

val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_CANCEL_CURRENT
}

val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(iconId)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)

context.packageManager.getLaunchIntentForPackage(context.packageName)?.let { intent ->
builder.setContentIntent(
PendingIntent.getActivity(
context,
roomId.hashCode(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
.setContentIntent(
PendingIntent.getActivity(context, notifId, intent, flags)
)

NotificationManagerCompat.from(context).notify(notifId, builder.build())
}

/**
* Builds an intent carrying the push payload so that
* [NotificationPlugin.onIntent] can extract it via
* [TauriNotificationManager.handleNotificationActionPerformed] and
* [NotificationPlugin.extractLocalNotificationData].
*
* Mirrors the structure set by [TauriNotificationManager.buildIntent] in the
* warm path (JS-triggered sendNotification). Because [UnifiedPushNotifier]
* runs when [NotificationPlugin.instance] is null (cold start), we replicate
* the intent extras rather than calling into the not-yet-initialized plugin.
*/
private fun buildPushIntent(
context: Context,
notifId: Int,
roomId: String,
eventId: String,
userId: String
): Intent {
val intent = context.packageManager
.getLaunchIntentForPackage(context.packageName)!!
intent.action = Intent.ACTION_MAIN
intent.addCategory(Intent.CATEGORY_LAUNCHER)
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
intent.putExtra(NOTIFICATION_INTENT_KEY, notifId)
intent.putExtra(ACTION_INTENT_KEY, DEFAULT_PRESS_ACTION)
intent.putExtra(NOTIFICATION_IS_REMOVABLE_KEY, true)

// Build the sourceJson that extractLocalNotificationData expects:
// a JSON object with "id" and "extra" containing room context.
val extraJson = JSONObject().apply {
put("room_id", roomId)
put("event_id", eventId)
if (userId.isNotEmpty()) put("user_id", userId)
}
val sourceJson = JSONObject().apply {
put("id", notifId)
put("extra", extraJson)
}.toString()
intent.putExtra(NOTIFICATION_OBJ_INTENT_KEY, sourceJson)

val id = (roomId.ifEmpty { notification.optString("event_id") }).hashCode()
NotificationManagerCompat.from(context).notify(id, builder.build())
return intent
}

private fun buildBody(notification: JSONObject, sender: String): String {
Expand Down