From 2726eb497b0b63921b9f566c0d3984644c511a6f Mon Sep 17 00:00:00 2001 From: Enzo Chiaro Date: Fri, 13 Feb 2026 12:26:57 +0100 Subject: [PATCH 1/3] feat: improve recording notification Use standard notification layout to match system notifications. Use regular actions with texts. Removed icons parameters. The notification is now silent to not pop when the user starts recording. Add parameters to customize the channel name and description. --- .../notification/NotificationRegistry.kt | 2 +- .../notification/RecordingNotification.kt | 94 +++++++------------ .../state/RecordingNotificationState.kt | 6 +- .../src/main/res/layout/btn_round_ripple.xml | 9 -- .../res/layout/notification_collapsed.xml | 45 --------- .../main/res/layout/notification_expanded.xml | 44 --------- .../src/system/notification/types.ts | 6 +- 7 files changed, 41 insertions(+), 165 deletions(-) delete mode 100644 packages/react-native-audio-api/android/src/main/res/layout/btn_round_ripple.xml delete mode 100644 packages/react-native-audio-api/android/src/main/res/layout/notification_collapsed.xml delete mode 100644 packages/react-native-audio-api/android/src/main/res/layout/notification_expanded.xml diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt index 0ab29555d..0fa8d5c6c 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt @@ -131,7 +131,7 @@ class NotificationRegistry( reactContext, audioAPIModule, RecordingNotification.ID, - "audio_recording4", + "audio_recording5", ) } diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt index 38924a24a..5abab90b0 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt @@ -79,12 +79,12 @@ class RecordingNotification( } override fun show(options: ReadableMap?): Notification { - initializeNotification() - val context = reactContext.get() ?: throw IllegalStateException("React context is null") if (options != state.cachedRNOptions) { state.cachedRNOptions = options parseMapFromRN(options) } + initializeNotification() + val context = reactContext.get() ?: throw IllegalStateException("React context is null") val builder = getBuilder() if (state.smallIconResourceName != null) { @@ -104,19 +104,13 @@ class RecordingNotification( builder.setColor(state.backgroundColor!!) } - val collapsedView = RemoteViews(context.packageName, R.layout.notification_collapsed) - val expandedView = RemoteViews(context.packageName, R.layout.notification_expanded) - - val (pauseResumePendingIntent, iconId) = setupPauseResumeIntent(context) - - setupRemoteView(listOf(collapsedView, expandedView), pauseResumePendingIntent, iconId) + val (pauseResumePendingIntent, iconId, actionText) = setupPauseResumeIntent(context) builder - .setStyle(NotificationCompat.DecoratedCustomViewStyle()) - .setCustomContentView(collapsedView) - .setCustomBigContentView(expandedView) .setContentTitle(state.title) .setContentText(state.contentText) + .setSilent(true) + .addAction(iconId, actionText, pauseResumePendingIntent) if (state.backgroundColor != null) { builder.setColor(state.backgroundColor!!) @@ -125,7 +119,7 @@ class RecordingNotification( return builder.build() } - private fun setupPauseResumeIntent(context: Context): Pair { + private fun setupPauseResumeIntent(context: Context): Triple { val pauseResumeIntent = if (state.paused) { state.resumeIntent @@ -141,41 +135,9 @@ class RecordingNotification( PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) - val pauseId = - if (state.pauseIconResourceName != null) { - context.resources.getIdentifier(state.pauseIconResourceName, "drawable", context.packageName) - } else { - android.R.drawable.ic_media_pause - } - val resumeId = - if (state.resumeIconResourceName != null) { - context.resources.getIdentifier(state.resumeIconResourceName, "drawable", context.packageName) - } else { - android.R.drawable.ic_media_play - } - - val iconId = if (state.paused) resumeId else pauseId - return pauseResumePendingIntent to iconId - } - - private fun setupRemoteView( - views: List, - pauseResumePendingIntent: PendingIntent, - iconId: Int, - ) { - val iconColor = - if (state.darkTheme) { - Color.WHITE // Dark Mode -> White Icon - } else { - Color.BLACK // Light Mode -> Black Icon - } - for (view in views) { - view.setTextViewText(R.id.notification_title, state.title) - view.setTextViewText(R.id.notification_content, state.contentText) - view.setImageViewResource(R.id.notification_action_btn, iconId) - view.setInt(R.id.notification_action_btn, "setColorFilter", iconColor) - view.setOnClickPendingIntent(R.id.notification_action_btn, pauseResumePendingIntent) - } + val iconId = if (state.paused) android.R.drawable.ic_media_play else android.R.drawable.ic_media_pause + val text = if (state.paused) state.resumeText else state.pauseText + return Triple(pauseResumePendingIntent, iconId, text) } // not used currently, left for future reference @@ -225,10 +187,10 @@ class RecordingNotification( val channel = NotificationChannel( channelId, - "Recording Audio", - NotificationManager.IMPORTANCE_LOW, + state.channelName, + NotificationManager.IMPORTANCE_HIGH, ).apply { - description = "Notifications for ongoing audio recordings" + description = state.channelDescription lockscreenVisibility = Notification.VISIBILITY_PUBLIC } val notificationManager = @@ -246,6 +208,18 @@ class RecordingNotification( } else { state.contentText ?: "Audio recording is in progress/paused" } + state.channelName = + if (options?.hasKey("channelName") == true) { + options.getString("channelName") ?: state.channelName + } else { + state.channelName + } + state.channelDescription = + if (options?.hasKey("channelDescription") == true) { + options.getString("channelDescription") ?: state.channelDescription + } else { + state.channelDescription + } state.smallIconResourceName = if (options?.hasKey("smallIconResourceName") == true @@ -262,21 +236,17 @@ class RecordingNotification( } else { state.largeIconResourceName ?: null } - state.pauseIconResourceName = - if (options?.hasKey("pauseIconResourceName") == - true - ) { - options.getString("pauseIconResourceName") + state.pauseText = + if (options?.hasKey("pauseText") == true) { + options.getString("pauseText") ?: state.pauseText } else { - state.pauseIconResourceName ?: null + state.pauseText } - state.resumeIconResourceName = - if (options?.hasKey("resumeIconResourceName") == - true - ) { - options.getString("resumeIconResourceName") + state.resumeText = + if (options?.hasKey("resumeText") == true) { + options.getString("resumeText") ?: state.resumeText } else { - state.resumeIconResourceName ?: null + state.resumeText } state.backgroundColor = if (options?.hasKey("color") == true) options.getInt("color") else state.backgroundColor ?: null state.paused = if (options?.hasKey("paused") == true) options.getBoolean("paused") else false diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt index b204e3987..f0144d2e9 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt @@ -13,11 +13,13 @@ data class RecordingNotificationState( var resumeIntent: Intent? = null, var title: String? = null, var contentText: String? = null, + var channelName: String = "Recording Audio", + var channelDescription: String = "Notifications for ongoing audio recordings", var paused: Boolean = false, + var pauseText: String = "Pause", + var resumeText: String = "Resume", var smallIconResourceName: String? = null, var largeIconResourceName: String? = null, - var pauseIconResourceName: String? = null, - var resumeIconResourceName: String? = null, var backgroundColor: Int? = null, var cachedRNOptions: ReadableMap? = null, var darkTheme: Boolean, diff --git a/packages/react-native-audio-api/android/src/main/res/layout/btn_round_ripple.xml b/packages/react-native-audio-api/android/src/main/res/layout/btn_round_ripple.xml deleted file mode 100644 index f63d30fc6..000000000 --- a/packages/react-native-audio-api/android/src/main/res/layout/btn_round_ripple.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/packages/react-native-audio-api/android/src/main/res/layout/notification_collapsed.xml b/packages/react-native-audio-api/android/src/main/res/layout/notification_collapsed.xml deleted file mode 100644 index b1f6e9d93..000000000 --- a/packages/react-native-audio-api/android/src/main/res/layout/notification_collapsed.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/react-native-audio-api/android/src/main/res/layout/notification_expanded.xml b/packages/react-native-audio-api/android/src/main/res/layout/notification_expanded.xml deleted file mode 100644 index 15f953d6c..000000000 --- a/packages/react-native-audio-api/android/src/main/res/layout/notification_expanded.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/react-native-audio-api/src/system/notification/types.ts b/packages/react-native-audio-api/src/system/notification/types.ts index 533146432..e7e7af9e3 100644 --- a/packages/react-native-audio-api/src/system/notification/types.ts +++ b/packages/react-native-audio-api/src/system/notification/types.ts @@ -65,10 +65,12 @@ export interface RecordingNotificationInfo { title?: string; contentText?: string; paused?: boolean; + channelName?: string; + channelDescription?: string; smallIconResourceName?: string; largeIconResourceName?: string; - pauseIconResourceName?: string; - resumeIconResourceName?: string; + pauseText?: string; + resumeText?: string; color?: number; } From 72efd14339822d7e77d18ea304628dbce7837597 Mon Sep 17 00:00:00 2001 From: Enzo Chiaro Date: Fri, 13 Feb 2026 14:13:16 +0100 Subject: [PATCH 2/3] fix: multiple actions added --- .../notification/RecordingNotification.kt | 22 +++++++++---------- .../state/RecordingNotificationState.kt | 2 -- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt index 5abab90b0..d2730b06d 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt @@ -166,20 +166,19 @@ class RecordingNotification( private fun getBuilder(): NotificationCompat.Builder { val context = reactContext.get() ?: throw IllegalStateException("React context is null") - if (state.builder == null) { - val openAppIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) - val pendingIntent = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_IMMUTABLE) + val openAppIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) + val pendingIntent = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_IMMUTABLE) + + val builder = + NotificationCompat + .Builder(context, channelId) + .setOngoing(true) + .setContentIntent(pendingIntent) - state.builder = - NotificationCompat - .Builder(context, channelId) - .setOngoing(true) - .setContentIntent(pendingIntent) - } if (state.smallIconResourceName == null) { - state.builder!!.setSmallIcon(android.R.drawable.ic_btn_speak_now) + builder.setSmallIcon(android.R.drawable.ic_btn_speak_now) } - return state.builder!! + return builder } private fun createNotificationChannel(context: ReactApplicationContext) { @@ -262,7 +261,6 @@ class RecordingNotification( val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(notificationId) state.initialized = false - state.builder = null } override fun getNotificationId(): Int = notificationId diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt index f0144d2e9..8d1ff7d24 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt @@ -1,12 +1,10 @@ package com.swmansion.audioapi.system.notification.state import android.content.Intent -import androidx.core.app.NotificationCompat import com.facebook.react.bridge.ReadableMap import com.swmansion.audioapi.system.notification.RecordingNotificationReceiver data class RecordingNotificationState( - var builder: NotificationCompat.Builder? = null, var receiver: RecordingNotificationReceiver? = null, var initialized: Boolean, var pauseIntent: Intent? = null, From f16817ed43716271bb6e0dbbbfc069aca957f864 Mon Sep 17 00:00:00 2001 From: Enzo Chiaro Date: Thu, 19 Feb 2026 11:14:25 +0100 Subject: [PATCH 3/3] fix: update example & doc --- apps/common-app/src/demos/Record/Record.tsx | 2 -- .../docs/system/recording-notification-manager.mdx | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/common-app/src/demos/Record/Record.tsx b/apps/common-app/src/demos/Record/Record.tsx index 7d9def11a..60b300878 100644 --- a/apps/common-app/src/demos/Record/Record.tsx +++ b/apps/common-app/src/demos/Record/Record.tsx @@ -37,8 +37,6 @@ const Record: FC = () => { contentText: paused ? 'Paused recording' : 'Recording...', paused, smallIconResourceName: 'logo', - pauseIconResourceName: 'pause', - resumeIconResourceName: 'resume', color: 0xff6200, }); }; diff --git a/packages/audiodocs/docs/system/recording-notification-manager.mdx b/packages/audiodocs/docs/system/recording-notification-manager.mdx index e2e384110..5205179f1 100644 --- a/packages/audiodocs/docs/system/recording-notification-manager.mdx +++ b/packages/audiodocs/docs/system/recording-notification-manager.mdx @@ -19,8 +19,8 @@ RecordingNotificationManager.show({ contentText: 'Recording...', paused: false, smallIconResourceName: 'icon_to_display', - pauseIconResourceName: 'pause_icon', - resumeIconResourceName: 'resume_icon', + pauseText: 'pause', + resumeText: 'resume', color: 0xff6200, }); @@ -99,8 +99,8 @@ interface RecordingNotificationInfo { paused?: boolean; // flag indicating whether to display pauseIcon or resumeIcon smallIconResourceName?: string; largeIconResourceName?: string; - pauseIconResourceName?: string; - resumeIconResourceName?: string; + pauseText?: string; + resumeText?: string; color?: number; // } ```