rns-nativecall 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,14 +6,15 @@ import com.facebook.react.bridge.Arguments
6
6
  import com.facebook.react.jstasks.HeadlessJsTaskConfig
7
7
 
8
8
  class CallHeadlessTask : HeadlessJsTaskService() {
9
- override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? {
10
- val extras = intent.extras
9
+ // Note: The '?' after Intent and the return type are specific in Kotlin
10
+ override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
11
+ val extras = intent?.extras
11
12
  return if (extras != null) {
12
13
  HeadlessJsTaskConfig(
13
- "ColdStartCallTask", // This name must match JS registration
14
+ "ColdStartCallTask",
14
15
  Arguments.fromBundle(extras),
15
- 5000, // Timeout for the task
16
- true // Allowed in foreground
16
+ 30000,
17
+ true
17
18
  )
18
19
  } else {
19
20
  null
@@ -6,47 +6,87 @@ import android.content.Intent
6
6
  import android.os.Bundle
7
7
  import android.os.Handler
8
8
  import android.os.Looper
9
+ import android.app.NotificationManager
10
+ import androidx.core.app.NotificationCompat
9
11
  import com.google.firebase.messaging.FirebaseMessagingService
10
12
  import com.google.firebase.messaging.RemoteMessage
11
13
  import com.facebook.react.HeadlessJsTaskService
12
14
 
13
15
  class CallMessagingService : FirebaseMessagingService() {
14
16
 
17
+ companion object {
18
+ private val handler = Handler(Looper.getMainLooper())
19
+ private val pendingNotifications = mutableMapOf<String, Runnable>()
20
+ // This is the standard ID used for the incoming call notification pill
21
+ private const val CALL_NOTIFICATION_ID = 123
22
+ }
23
+
15
24
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
16
25
  val data = remoteMessage.data
17
26
  val context = applicationContext
18
27
 
28
+ val uuid = data["callUuid"] ?: return
29
+ val type = data["type"] ?: ""
30
+
31
+ if (type == "CANCEL") {
32
+ pendingNotifications[uuid]?.let {
33
+ handler.removeCallbacks(it)
34
+ pendingNotifications.remove(uuid)
35
+ }
36
+
37
+ // 1. Stop the ringtone
38
+ NativeCallManager.stopRingtone()
39
+
40
+ // 2. DISMISS THE PILL (Direct Native Way)
41
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
42
+ notificationManager.cancel(CALL_NOTIFICATION_ID)
43
+
44
+ // 3. Show Missed Call
45
+ showMissedCallNotification(context, data, uuid)
46
+ return
47
+ }
48
+
19
49
  if (isAppInForeground(context)) {
20
- // App is visible: Send event to active JS bridge
21
50
  CallModule.sendEventToJS("onCallReceived", data)
22
51
  } else {
23
- // App is killed/background: Start the Headless JS Task
24
- // This wakes up the JS engine and connects your WebSocket
25
- startHeadlessTask(context, data)
26
-
27
- // Start the 18-second "System Pill" backup timer
28
- val handler = Handler(Looper.getMainLooper())
29
- handler.postDelayed({
30
- // Final safety check: if the user hasn't brought the app to foreground,
31
- // show the native full-screen notification (The Pill)
52
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
53
+ launchIntent?.apply {
54
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
55
+ putExtra("background_wake", true)
56
+ }
57
+ context.startActivity(launchIntent)
58
+
59
+ val showNotificationRunnable = Runnable {
32
60
  if (!isAppInForeground(context)) {
33
61
  NativeCallManager.handleIncomingPush(context, data)
34
62
  }
35
- }, 18000)
63
+ pendingNotifications.remove(uuid)
64
+ }
65
+
66
+ pendingNotifications[uuid] = showNotificationRunnable
67
+ handler.postDelayed(showNotificationRunnable, 18000)
36
68
  }
37
69
  }
38
70
 
39
- private fun startHeadlessTask(context: Context, data: Map<String, String>) {
40
- val serviceIntent = Intent(context, CallHeadlessTask::class.java)
41
- val bundle = Bundle()
42
-
43
- // Pass all FCM data to the JS task
44
- data.forEach { (key, value) -> bundle.putString(key, value) }
45
- serviceIntent.putExtras(bundle)
46
-
47
- // Start the service. This triggers the 'ColdStartCallTask' in index.js
48
- context.startService(serviceIntent)
49
- HeadlessJsTaskService.acquireWakeLockNow(context)
71
+ private fun showMissedCallNotification(context: Context, data: Map<String, String>, uuid: String) {
72
+ val name = data["name"] ?: "Unknown"
73
+ val callType = data["callType"] ?: "video"
74
+ val channelId = "missed_calls"
75
+
76
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
77
+
78
+ var iconResId = context.resources.getIdentifier("ic_missed_call", "drawable", context.packageName)
79
+ if (iconResId == 0) iconResId = android.R.drawable.sym_call_missed
80
+
81
+ val builder = NotificationCompat.Builder(context, channelId)
82
+ .setSmallIcon(iconResId)
83
+ .setContentTitle("Missed $callType call")
84
+ .setContentText("You missed a call from $name")
85
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
86
+ .setAutoCancel(true)
87
+ .setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
88
+
89
+ notificationManager.notify(uuid.hashCode(), builder.build())
50
90
  }
51
91
 
52
92
  private fun isAppInForeground(context: Context): Boolean {
@@ -55,9 +95,6 @@ class CallMessagingService : FirebaseMessagingService() {
55
95
  val packageName = context.packageName
56
96
 
57
97
  for (appProcess in appProcesses) {
58
- // IMPORTANCE_FOREGROUND only triggers if a real Activity is on screen.
59
- // Running a Headless Task counts as IMPORTANCE_VISIBLE or lower,
60
- // so this check remains accurate.
61
98
  if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
62
99
  appProcess.processName == packageName) {
63
100
  return true
@@ -28,7 +28,6 @@ object NativeCallManager {
28
28
  } else {
29
29
  PendingIntent.FLAG_UPDATE_CURRENT
30
30
  }
31
-
32
31
  // 1. IMPROVED INTENT: Direct to AcceptCallActivity
33
32
  // This is better than a dummy intent because it allows the OS to
34
33
  // launch your "Accept" logic immediately if the phone is locked.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "RNS nativecall component with native Android/iOS for handling native call ui, when app is not open or open.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -5,10 +5,15 @@ function withMainActivityDataFix(config) {
5
5
  return withMainActivity(config, (config) => {
6
6
  let contents = config.modResults.contents;
7
7
 
8
+ // 1. Ensure necessary Imports are present
8
9
  if (!contents.includes('import android.content.Intent')) {
9
10
  contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
10
11
  }
12
+ if (!contents.includes('import android.os.Bundle')) {
13
+ contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.os.Bundle`);
14
+ }
11
15
 
16
+ // 2. Define the code blocks
12
17
  const onNewIntentCode = `
13
18
  override fun onNewIntent(intent: Intent) {
14
19
  super.onNewIntent(intent)
@@ -16,11 +21,28 @@ function withMainActivityDataFix(config) {
16
21
  }
17
22
  `;
18
23
 
24
+ const onCreateCode = `
25
+ override fun onCreate(savedInstanceState: Bundle?) {
26
+ super.onCreate(savedInstanceState)
27
+ // If woken up silently, move to back to prevent UI flicker
28
+ if (intent.getBooleanExtra("background_wake", false)) {
29
+ moveTaskToBack(true)
30
+ }
31
+ }
32
+ `;
33
+
34
+ // 3. Insert the codes before the last closing brace of the class
19
35
  if (!contents.includes('override fun onNewIntent')) {
20
36
  const lastBraceIndex = contents.lastIndexOf('}');
21
37
  contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
22
38
  }
23
39
 
40
+ if (!contents.includes('override fun onCreate')) {
41
+ // Re-calculate lastBraceIndex because contents string has changed
42
+ const lastBraceIndex = contents.lastIndexOf('}');
43
+ contents = contents.slice(0, lastBraceIndex) + onCreateCode + contents.slice(lastBraceIndex);
44
+ }
45
+
24
46
  config.modResults.contents = contents;
25
47
  return config;
26
48
  });
@@ -104,6 +126,8 @@ function withAndroidConfig(config) {
104
126
  });
105
127
  }
106
128
 
129
+
130
+
107
131
  return config;
108
132
  });
109
133
  }