package ai.sirrus.reactnativesdk import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import java.net.HttpURLConnection import java.net.URL import kotlin.math.abs object SirrusNotificationRenderer { private const val DEFAULT_CHANNEL_ID = "sirrus-default" private const val DEFAULT_CHANNEL_NAME = "Sirrus Notifications" @JvmStatic fun show(context: Context, payload: SirrusNotificationPayload) { val channelId = payload.channelId ?: DEFAULT_CHANNEL_ID ensureNotificationChannel(context, channelId) val messageId = payload.messageId ?: (payload.title + payload.body) val notificationId = abs(messageId.hashCode()) val pendingIntent = buildInteractionPendingIntent( context = context, payload = payload, interactionType = "opened", action = null, launchApp = true, notificationId = notificationId, requestCode = abs((messageId + ":open").hashCode()) ) val notificationIcon = resolveSmallIcon(context) val builder = NotificationCompat.Builder(context, channelId) .setSmallIcon(notificationIcon) .setContentTitle(payload.title) .setContentText(payload.body) .setStyle(NotificationCompat.BigTextStyle().bigText(payload.body)) .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent) if (!payload.subtitle.isNullOrBlank()) { builder.setSubText(payload.subtitle) } val mediaUrl = payload.imageUrl ?: payload.gifUrl ?: payload.videoUrl if (!mediaUrl.isNullOrBlank()) { downloadBitmap(mediaUrl)?.let { bitmap -> builder.setLargeIcon(bitmap) builder.setStyle( NotificationCompat.BigPictureStyle() .bigPicture(bitmap) .bigLargeIcon(null as Bitmap?) ) } } for (action in payload.actions) { val actionPendingIntent = buildInteractionPendingIntent( context = context, payload = payload, interactionType = "action_pressed", action = action, launchApp = action.foreground, notificationId = notificationId, requestCode = abs((messageId + ":" + action.id).hashCode()) ) builder.addAction( notificationIcon, action.title, actionPendingIntent ) } NotificationManagerCompat.from(context).notify( notificationId, builder.build() ) } @JvmStatic fun parseImportance(value: String?): Int { return when (value) { "min" -> NotificationManager.IMPORTANCE_MIN "low" -> NotificationManager.IMPORTANCE_LOW "high" -> NotificationManager.IMPORTANCE_HIGH "max" -> NotificationManager.IMPORTANCE_MAX else -> NotificationManager.IMPORTANCE_DEFAULT } } private fun pendingIntentMutabilityFlag(): Int { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_IMMUTABLE } else { 0 } } private fun resolveSmallIcon(context: Context): Int { val notificationIcon = context.resources.getIdentifier( "small_icon", "drawable", context.packageName ) return if (notificationIcon != 0) { notificationIcon } else { context.applicationInfo.icon } } private fun ensureNotificationChannel(context: Context, channelId: String) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { return } val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (manager.getNotificationChannel(channelId) != null) { return } val channel = NotificationChannel( channelId, if (channelId == DEFAULT_CHANNEL_ID) DEFAULT_CHANNEL_NAME else channelId, NotificationManager.IMPORTANCE_HIGH ) manager.createNotificationChannel(channel) } private fun buildReceiverIntent( context: Context, payload: SirrusNotificationPayload, interactionType: String, action: SirrusNotificationAction?, launchApp: Boolean ): Intent { return Intent(context, SirrusNotificationActionReceiver::class.java).apply { putInteractionExtras( payload = payload, interactionType = interactionType, action = action, launchApp = launchApp, notificationId = abs((payload.messageId ?: (payload.title + payload.body)).hashCode()) ) } } private fun buildInteractionPendingIntent( context: Context, payload: SirrusNotificationPayload, interactionType: String, action: SirrusNotificationAction?, launchApp: Boolean, notificationId: Int, requestCode: Int ): PendingIntent { val flags = PendingIntent.FLAG_UPDATE_CURRENT or pendingIntentMutabilityFlag() if (launchApp) { val launchIntent = buildLaunchIntent( context = context, payload = payload, interactionType = interactionType, action = action, notificationId = notificationId ) if (launchIntent != null) { return PendingIntent.getActivity(context, requestCode, launchIntent, flags) } } val receiverIntent = buildReceiverIntent( context = context, payload = payload, interactionType = interactionType, action = action, launchApp = launchApp ) return PendingIntent.getBroadcast(context, requestCode, receiverIntent, flags) } private fun buildLaunchIntent( context: Context, payload: SirrusNotificationPayload, interactionType: String, action: SirrusNotificationAction?, notificationId: Int ): Intent? { val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) ?: return null val deeplinkUrl = action?.deeplinkUrl ?: payload.deeplinkUrl return launchIntent.apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) if (!deeplinkUrl.isNullOrBlank()) { putExtra("sirrus_deeplink_url", deeplinkUrl) data = android.net.Uri.parse(deeplinkUrl) } putInteractionExtras( payload = payload, interactionType = interactionType, action = action, launchApp = true, notificationId = notificationId ) } } private fun Intent.putInteractionExtras( payload: SirrusNotificationPayload, interactionType: String, action: SirrusNotificationAction?, launchApp: Boolean, notificationId: Int ) { val eventJson = buildInteractionEvent( payload = payload, interactionType = interactionType, action = action, notificationId = notificationId ) putExtra(SirrusNotificationEventDispatcher.EVENT_EXTRA_KEY, eventJson.toString()) putExtra("interactionType", interactionType) putExtra("actionId", action?.id) putExtra("actionTitle", action?.title) putExtra("actionLabel", action?.label ?: action?.title) putExtra("actionType", action?.actionType) putExtra("actionPostback", action?.postback) putExtra("actionDeeplinkUrl", action?.deeplinkUrl) putExtra("actionForeground", action?.foreground ?: launchApp) putExtra("actionDestructive", action?.destructive ?: false) putExtra("actionAuthenticationRequired", action?.authenticationRequired ?: false) putExtra("launchApp", launchApp) putExtra("sirrusNotificationId", notificationId) putExtra("payloadJson", payload.toEventJson().toString()) putExtra("title", payload.title) putExtra("body", payload.body) putExtra("subtitle", payload.subtitle) putExtra("messageId", payload.messageId) putExtra("campaignId", payload.campaignId) putExtra("templateId", payload.templateId) putExtra("deeplinkUrl", payload.deeplinkUrl) putExtra("provider", payload.provider) } private fun buildInteractionEvent( payload: SirrusNotificationPayload, interactionType: String, action: SirrusNotificationAction?, notificationId: Int ) = org.json.JSONObject().apply { put("type", interactionType) put("actionId", action?.id ?: "") put("deeplinkUrl", action?.deeplinkUrl ?: payload.deeplinkUrl ?: "") put("notificationId", notificationId) put("payload", payload.toEventJson()) if (action != null) { put("action", action.toEventJson()) } } private fun downloadBitmap(urlString: String): Bitmap? { return try { val connection = URL(urlString).openConnection() as HttpURLConnection connection.connectTimeout = 5000 connection.readTimeout = 5000 connection.doInput = true connection.connect() connection.inputStream.use { input -> BitmapFactory.decodeStream(input) } } catch (_: Throwable) { null } } }