mobiler 0.32.0

Build mobile apps in Rust — one core, native UI on Android, iOS, and the web (CLI)
package {{PACKAGE}}

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import org.json.JSONObject

// FCM entry point — runs in the app process even when no UI is alive (FCM starts it). Statically
// declared in the manifest by the plugin (a <service> with the MESSAGING_EVENT intent-filter).
//   onNewToken       → push a {"type":"token_refresh","token":…} event so the app re-POSTs to its backend.
//   onMessageReceived→ build a tray notification (data messages don't auto-display) + emit the payload.
// Both go through PushBus, which buffers until the core subscribes. Send DATA messages from your
// backend so the app controls display + the event stream fires consistently (foreground + background).
private const val CHANNEL_ID = "mobiler_push"

class PushMessagingService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        PushBus.emit("""{"type":"token_refresh","token":"$token"}""")
    }

    override fun onMessageReceived(message: RemoteMessage) {
        // Merge the data map (+ any notification title/body) into one JSON payload for the app.
        val payload = JSONObject()
        for ((k, v) in message.data) payload.put(k, v)
        message.notification?.let { n ->
            n.title?.let { payload.put("title", it) }
            n.body?.let { payload.put("body", it) }
        }
        val json = payload.toString()

        // Post a tray notification so the user sees something even for a data-only message.
        ensureChannel(this)
        val title = message.notification?.title ?: message.data["title"] ?: "Notification"
        val body = message.notification?.body ?: message.data["body"] ?: ""
        val notif = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentTitle(title)
            .setContentText(body)
            .setAutoCancel(true)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .build()
        runCatching { NotificationManagerCompat.from(this).notify(System.currentTimeMillis().toInt(), notif) }

        PushBus.emit(json)
    }
}

private fun ensureChannel(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val mgr = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        if (mgr.getNotificationChannel(CHANNEL_ID) == null) {
            mgr.createNotificationChannel(
                NotificationChannel(CHANNEL_ID, "Push", NotificationManager.IMPORTANCE_HIGH)
            )
        }
    }
}