@qusaieilouti99/call-manager 0.1.0

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.
Files changed (28) hide show
  1. package/CallManager.podspec +21 -0
  2. package/LICENSE +20 -0
  3. package/README.md +33 -0
  4. package/android/build.gradle +83 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +22 -0
  7. package/android/src/main/java/com/qusaieilouti99/callmanager/CallForegroundService.kt +56 -0
  8. package/android/src/main/java/com/qusaieilouti99/callmanager/CallManagerModule.kt +123 -0
  9. package/android/src/main/java/com/qusaieilouti99/callmanager/CallManagerPackage.kt +33 -0
  10. package/android/src/main/java/com/qusaieilouti99/callmanager/CallNotificationActionReceiver.kt +27 -0
  11. package/android/src/main/java/com/qusaieilouti99/callmanager/CallNotificationHelper.kt +138 -0
  12. package/android/src/main/java/com/qusaieilouti99/callmanager/MyConnection.kt +53 -0
  13. package/android/src/main/java/com/qusaieilouti99/callmanager/MyConnectionService.kt +38 -0
  14. package/ios/CallManager.h +5 -0
  15. package/ios/CallManager.mm +18 -0
  16. package/lib/module/NativeCallManager.js +5 -0
  17. package/lib/module/NativeCallManager.js.map +1 -0
  18. package/lib/module/index.js +5 -0
  19. package/lib/module/index.js.map +1 -0
  20. package/lib/module/package.json +1 -0
  21. package/lib/typescript/package.json +1 -0
  22. package/lib/typescript/src/NativeCallManager.d.ts +12 -0
  23. package/lib/typescript/src/NativeCallManager.d.ts.map +1 -0
  24. package/lib/typescript/src/index.d.ts +3 -0
  25. package/lib/typescript/src/index.d.ts.map +1 -0
  26. package/package.json +164 -0
  27. package/src/NativeCallManager.ts +13 -0
  28. package/src/index.tsx +3 -0
@@ -0,0 +1,21 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "CallManager"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/qusaieilouti99/call-manager.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm,cpp}"
17
+ s.private_header_files = "ios/**/*.h"
18
+
19
+
20
+ install_modules_dependencies(s)
21
+ end
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Qusai Eilouti ( Script )
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @qusaieilouti99/call-manager
2
+
3
+ call manager
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @qusaieilouti99/call-manager
9
+ ```
10
+
11
+ ## Usage
12
+
13
+
14
+ ```js
15
+ import { multiply } from '@qusaieilouti99/call-manager';
16
+
17
+ // ...
18
+
19
+ const result = multiply(3, 7);
20
+ ```
21
+
22
+
23
+ ## Contributing
24
+
25
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
26
+
27
+ ## License
28
+
29
+ MIT
30
+
31
+ ---
32
+
33
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,83 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['CallManager_' + name]
4
+ }
5
+
6
+ repositories {
7
+ google()
8
+ mavenCentral()
9
+ }
10
+
11
+ dependencies {
12
+ classpath "com.android.tools.build:gradle:8.7.2"
13
+ // noinspection DifferentKotlinGradleVersion
14
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
15
+ }
16
+ }
17
+
18
+
19
+ apply plugin: "com.android.library"
20
+ apply plugin: "kotlin-android"
21
+
22
+ apply plugin: "com.facebook.react"
23
+
24
+ def getExtOrIntegerDefault(name) {
25
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["CallManager_" + name]).toInteger()
26
+ }
27
+
28
+ android {
29
+ namespace "com.qusaieilouti99.callmanager"
30
+
31
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
32
+
33
+ defaultConfig {
34
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
35
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
36
+ }
37
+
38
+ buildFeatures {
39
+ buildConfig true
40
+ }
41
+
42
+ buildTypes {
43
+ release {
44
+ minifyEnabled false
45
+ }
46
+ }
47
+
48
+ lintOptions {
49
+ disable "GradleCompatible"
50
+ }
51
+
52
+ compileOptions {
53
+ sourceCompatibility JavaVersion.VERSION_1_8
54
+ targetCompatibility JavaVersion.VERSION_1_8
55
+ }
56
+
57
+ sourceSets {
58
+ main {
59
+ java.srcDirs += [
60
+ "generated/java",
61
+ "generated/jni"
62
+ ]
63
+ }
64
+ }
65
+ }
66
+
67
+ repositories {
68
+ mavenCentral()
69
+ google()
70
+ }
71
+
72
+ def kotlin_version = getExtOrDefault("kotlinVersion")
73
+
74
+ dependencies {
75
+ implementation "com.facebook.react:react-android"
76
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
77
+ }
78
+
79
+ react {
80
+ jsRootDir = file("../src/")
81
+ libraryName = "CallManager"
82
+ codegenJavaPackageName = "com.qusaieilouti99.callmanager"
83
+ }
@@ -0,0 +1,5 @@
1
+ CallManager_kotlinVersion=2.0.21
2
+ CallManager_minSdkVersion=24
3
+ CallManager_targetSdkVersion=34
4
+ CallManager_compileSdkVersion=35
5
+ CallManager_ndkVersion=27.1.12297006
@@ -0,0 +1,22 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.qusaieilouti99.callmanager">
3
+
4
+ <application>
5
+ <service
6
+ android:name=".MyConnectionService"
7
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
8
+ android:exported="true">
9
+ <intent-filter>
10
+ <action android:name="android.telecom.ConnectionService" />
11
+ </intent-filter>
12
+ </service>
13
+ <service
14
+ android:name=".CallForegroundService"
15
+ android:enabled="true"
16
+ android:exported="false"
17
+ android:foregroundServiceType="phoneCall" />
18
+ <receiver
19
+ android:name=".CallNotificationActionReceiver"
20
+ android:exported="false" />
21
+ </application>
22
+ </manifest>
@@ -0,0 +1,56 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import android.app.Notification
4
+ import android.app.NotificationChannel
5
+ import android.app.NotificationManager
6
+ import android.app.Service
7
+ import android.content.Intent
8
+ import android.os.Build
9
+ import android.os.IBinder
10
+
11
+ class CallForegroundService : Service() {
12
+
13
+ companion object {
14
+ const val CHANNEL_ID = "call_foreground_channel"
15
+ const val NOTIFICATION_ID = 1001
16
+ }
17
+
18
+ override fun onCreate() {
19
+ super.onCreate()
20
+ createNotificationChannel()
21
+ }
22
+
23
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
24
+ val notification = buildNotification()
25
+ startForeground(NOTIFICATION_ID, notification)
26
+ return START_STICKY
27
+ }
28
+
29
+ override fun onBind(intent: Intent?): IBinder? = null
30
+
31
+ private fun buildNotification(): Notification {
32
+ val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
33
+ Notification.Builder(this, CHANNEL_ID)
34
+ } else {
35
+ Notification.Builder(this)
36
+ }
37
+ builder
38
+ .setContentTitle("Ongoing Call")
39
+ .setContentText("You are in a call")
40
+ .setSmallIcon(android.R.drawable.sym_call_incoming)
41
+ .setOngoing(true)
42
+ return builder.build()
43
+ }
44
+
45
+ private fun createNotificationChannel() {
46
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
47
+ val channel = NotificationChannel(
48
+ CHANNEL_ID,
49
+ "Call Foreground Channel",
50
+ NotificationManager.IMPORTANCE_LOW
51
+ )
52
+ val manager = getSystemService(NotificationManager::class.java)
53
+ manager.createNotificationChannel(channel)
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,123 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import android.app.ActivityManager
4
+ import android.content.ComponentName
5
+ import android.content.Context
6
+ import android.content.Intent
7
+ import android.os.Build
8
+ import android.os.Bundle
9
+ import android.telecom.PhoneAccount
10
+ import android.telecom.PhoneAccountHandle
11
+ import android.telecom.TelecomManager
12
+ import android.util.Log
13
+ import com.facebook.react.bridge.*
14
+ import com.facebook.react.turbomodule.core.interfaces.TurboModule
15
+
16
+ class CallManagerModule(reactContext: ReactApplicationContext) :
17
+ NativeCallManagerSpec(reactContext), TurboModule {
18
+
19
+ companion object {
20
+ const val NAME = "CallManager"
21
+ const val TAG = "CallManagerModule"
22
+ const val PHONE_ACCOUNT_ID = "com.qusaieilouti99.callmanager.SELF_MANAGED"
23
+ }
24
+
25
+ private var currentCallId: String? = null
26
+ private var eventHandler: Callback? = null
27
+
28
+ override fun getName(): String = NAME
29
+
30
+ // Register JS event handler
31
+ override fun setEventHandler(callback: Callback) {
32
+ eventHandler = callback
33
+ }
34
+
35
+ // Helper to emit events to JS
36
+ fun emitEvent(event: String, callData: String) {
37
+ eventHandler?.invoke(event, callData)
38
+ }
39
+
40
+ // 1. Report incoming call
41
+ override fun reportIncomingCall(callId: String, callData: String) {
42
+ val context = reactApplicationContext
43
+ val telecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
44
+
45
+ // Register self-managed PhoneAccount if not already registered
46
+ val phoneAccountHandle = PhoneAccountHandle(
47
+ ComponentName(context, MyConnectionService::class.java),
48
+ PHONE_ACCOUNT_ID
49
+ )
50
+ if (telecomManager.getPhoneAccount(phoneAccountHandle) == null) {
51
+ val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "PingMe Call")
52
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
53
+ .build()
54
+ telecomManager.registerPhoneAccount(phoneAccount)
55
+ }
56
+
57
+ // Prepare extras
58
+ val extras = Bundle()
59
+ extras.putString(MyConnectionService.EXTRA_CALL_DATA, callData)
60
+
61
+ // Report the call
62
+ try {
63
+ telecomManager.addNewIncomingCall(phoneAccountHandle, extras)
64
+ currentCallId = callId
65
+ startCallForegroundService()
66
+ } catch (e: Exception) {
67
+ Log.e(TAG, "Failed to report incoming call: ${e.message}")
68
+ }
69
+ }
70
+
71
+ // 2. End call
72
+ override fun endCall(callId: String) {
73
+ stopCallForegroundService()
74
+ }
75
+
76
+ // 3. Answer call
77
+ override fun answerCall(callId: String) {
78
+ bringAppToForeground()
79
+ }
80
+
81
+ // 4. Silence ringtone
82
+ override fun silenceRingtone() {
83
+ // TODO: Implement ringtone silencing logic if you play a custom ringtone
84
+ }
85
+
86
+ // 5. Reject current and answer new
87
+ override fun rejectCurrentAndAnswerNew(callId: String, callData: String) {
88
+ endCall(currentCallId ?: "")
89
+ reportIncomingCall(callId, callData)
90
+ }
91
+
92
+ // --- Foreground Service Helpers ---
93
+
94
+ private fun startCallForegroundService() {
95
+ val context = reactApplicationContext
96
+ val intent = Intent(context, CallForegroundService::class.java)
97
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
98
+ context.startForegroundService(intent)
99
+ } else {
100
+ context.startService(intent)
101
+ }
102
+ }
103
+
104
+ private fun stopCallForegroundService() {
105
+ val context = reactApplicationContext
106
+ val intent = Intent(context, CallForegroundService::class.java)
107
+ context.stopService(intent)
108
+ }
109
+
110
+ // --- Bring App to Foreground ---
111
+
112
+ private fun bringAppToForeground() {
113
+ val context = reactApplicationContext
114
+ val packageName = context.packageName
115
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
116
+ val tasks = activityManager.appTasks
117
+ if (tasks.isNullOrEmpty()) {
118
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
119
+ launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
120
+ context.startActivity(launchIntent)
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,33 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import java.util.HashMap
9
+
10
+ class CallManagerPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == CallManagerModule.NAME) {
13
+ CallManagerModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20
+ return ReactModuleInfoProvider {
21
+ val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22
+ moduleInfos[CallManagerModule.NAME] = ReactModuleInfo(
23
+ CallManagerModule.NAME,
24
+ CallManagerModule.NAME,
25
+ false, // canOverrideExistingModule
26
+ false, // needsEagerInit
27
+ false, // isCxxModule
28
+ true // isTurboModule
29
+ )
30
+ moduleInfos
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,27 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+
7
+ class CallNotificationActionReceiver : BroadcastReceiver() {
8
+ override fun onReceive(context: Context, intent: Intent) {
9
+ val callId = intent.getStringExtra("callId") ?: return
10
+ val module = (context.applicationContext as? com.facebook.react.ReactApplication)
11
+ ?.reactNativeHost
12
+ ?.reactInstanceManager
13
+ ?.currentReactContext
14
+ ?.getNativeModule(CallManagerModule::class.java)
15
+
16
+ when (intent.action) {
17
+ "com.qusaieilouti99.callmanager.ANSWER_CALL" -> {
18
+ module?.answerCall(callId)
19
+ }
20
+ "com.qusaieilouti99.callmanager.DECLINE_CALL" -> {
21
+ module?.endCall(callId)
22
+ }
23
+ }
24
+ // Cancel notification and stop ringtone
25
+ CallNotificationHelper(context).cancelIncomingCallNotification()
26
+ }
27
+ }
@@ -0,0 +1,138 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import android.app.*
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.graphics.Color
7
+ import android.media.AudioAttributes
8
+ import android.media.RingtoneManager
9
+ import android.os.Build
10
+ import android.telecom.CallAttributes
11
+ import android.telecom.CallStyle
12
+ import android.telecom.CallAttributesCompat
13
+ import android.telecom.CallStyleCompat
14
+ import android.util.Log
15
+
16
+ class CallNotificationHelper(private val context: Context) {
17
+
18
+ companion object {
19
+ const val CHANNEL_ID = "incoming_call_channel"
20
+ const val NOTIFICATION_ID = 2001
21
+ }
22
+
23
+ private var ringtone: android.media.Ringtone? = null
24
+
25
+ fun showIncomingCallNotification(callId: String, callerName: String) {
26
+ createNotificationChannel()
27
+
28
+ // Intent for answer action
29
+ val answerIntent = Intent(context, CallNotificationActionReceiver::class.java).apply {
30
+ action = "com.qusaieilouti99.callmanager.ANSWER_CALL"
31
+ putExtra("callId", callId)
32
+ }
33
+ val answerPendingIntent = PendingIntent.getBroadcast(
34
+ context, 0, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
35
+ )
36
+
37
+ // Intent for decline action
38
+ val declineIntent = Intent(context, CallNotificationActionReceiver::class.java).apply {
39
+ action = "com.qusaieilouti99.callmanager.DECLINE_CALL"
40
+ putExtra("callId", callId)
41
+ }
42
+ val declinePendingIntent = PendingIntent.getBroadcast(
43
+ context, 1, declineIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
44
+ )
45
+
46
+ // Full-screen intent to pop up the app/call UI
47
+ val fullScreenIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
48
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
49
+ putExtra("callId", callId)
50
+ }
51
+ val fullScreenPendingIntent = PendingIntent.getActivity(
52
+ context, 2, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
53
+ )
54
+
55
+ // Build CallStyle notification (Android 12+)
56
+ val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
57
+ Notification.Builder(context, CHANNEL_ID)
58
+ .setStyle(
59
+ Notification.CallStyle.forIncomingCall(
60
+ callerName,
61
+ fullScreenPendingIntent,
62
+ answerPendingIntent,
63
+ declinePendingIntent
64
+ )
65
+ )
66
+ } else {
67
+ Notification.Builder(context, CHANNEL_ID)
68
+ .setContentTitle("Incoming Call")
69
+ .setContentText(callerName)
70
+ .setSmallIcon(android.R.drawable.sym_call_incoming)
71
+ .setPriority(Notification.PRIORITY_HIGH)
72
+ .setCategory(Notification.CATEGORY_CALL)
73
+ .setFullScreenIntent(fullScreenPendingIntent, true)
74
+ .addAction(android.R.drawable.sym_action_call, "Answer", answerPendingIntent)
75
+ .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Decline", declinePendingIntent)
76
+ }
77
+
78
+ val notification = builder
79
+ .setOngoing(true)
80
+ .setAutoCancel(false)
81
+ .build()
82
+
83
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
84
+ notificationManager.notify(NOTIFICATION_ID, notification)
85
+
86
+ playRingtone()
87
+ }
88
+
89
+ fun cancelIncomingCallNotification() {
90
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
91
+ notificationManager.cancel(NOTIFICATION_ID)
92
+ stopRingtone()
93
+ }
94
+
95
+ private fun createNotificationChannel() {
96
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
97
+ val channel = NotificationChannel(
98
+ CHANNEL_ID,
99
+ "Incoming Call Channel",
100
+ NotificationManager.IMPORTANCE_HIGH
101
+ )
102
+ channel.description = "Notifications for incoming calls"
103
+ channel.enableLights(true)
104
+ channel.lightColor = Color.GREEN
105
+ channel.enableVibration(true)
106
+ channel.setSound(
107
+ RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
108
+ AudioAttributes.Builder()
109
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
110
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
111
+ .build()
112
+ )
113
+ val manager = context.getSystemService(NotificationManager::class.java)
114
+ manager.createNotificationChannel(channel)
115
+ }
116
+ }
117
+
118
+ private fun playRingtone() {
119
+ try {
120
+ val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
121
+ ringtone = RingtoneManager.getRingtone(context, uri)
122
+ ringtone?.audioAttributes = AudioAttributes.Builder()
123
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
124
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
125
+ .build()
126
+ ringtone?.play()
127
+ } catch (e: Exception) {
128
+ Log.e("CallNotificationHelper", "Failed to play ringtone: ${e.message}")
129
+ }
130
+ }
131
+
132
+ fun stopRingtone() {
133
+ try {
134
+ ringtone?.stop()
135
+ } catch (_: Exception) {}
136
+ ringtone = null
137
+ }
138
+ }
@@ -0,0 +1,53 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import android.content.Context
4
+ import android.telecom.Connection
5
+ import android.telecom.DisconnectCause
6
+ import android.util.Log
7
+ import com.facebook.react.ReactApplication
8
+
9
+ class MyConnection(
10
+ private val context: Context,
11
+ private val callData: String
12
+ ) : Connection() {
13
+
14
+ companion object {
15
+ const val TAG = "MyConnection"
16
+ }
17
+
18
+ override fun onAnswer() {
19
+ Log.d(TAG, "Call answered")
20
+ setActive()
21
+ // Notify JS via TurboModule's event handler
22
+ (context.applicationContext as? ReactApplication)?.let { app ->
23
+ val module = app.reactNativeHost.reactInstanceManager
24
+ .currentReactContext
25
+ ?.getNativeModule(CallManagerModule::class.java)
26
+ module?.emitEvent("onCallAnswered", callData)
27
+ }
28
+ }
29
+
30
+ override fun onReject() {
31
+ Log.d(TAG, "Call rejected")
32
+ setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
33
+ destroy()
34
+ (context.applicationContext as? ReactApplication)?.let { app ->
35
+ val module = app.reactNativeHost.reactInstanceManager
36
+ .currentReactContext
37
+ ?.getNativeModule(CallManagerModule::class.java)
38
+ module?.emitEvent("onCallRejected", callData)
39
+ }
40
+ }
41
+
42
+ override fun onDisconnect() {
43
+ Log.d(TAG, "Call disconnected")
44
+ setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
45
+ destroy()
46
+ (context.applicationContext as? ReactApplication)?.let { app ->
47
+ val module = app.reactNativeHost.reactInstanceManager
48
+ .currentReactContext
49
+ ?.getNativeModule(CallManagerModule::class.java)
50
+ module?.emitEvent("onCallEnded", callData)
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,38 @@
1
+ package com.qusaieilouti99.callmanager
2
+
3
+ import android.telecom.Connection
4
+ import android.telecom.ConnectionRequest
5
+ import android.telecom.ConnectionService
6
+ import android.telecom.PhoneAccountHandle
7
+ import android.util.Log
8
+
9
+ class MyConnectionService : ConnectionService() {
10
+
11
+ companion object {
12
+ const val TAG = "MyConnectionService"
13
+ const val EXTRA_CALL_DATA = "callData"
14
+ }
15
+
16
+ override fun onCreateIncomingConnection(
17
+ phoneAccountHandle: PhoneAccountHandle,
18
+ request: ConnectionRequest
19
+ ): Connection {
20
+ Log.d(TAG, "onCreateIncomingConnection: ${request.extras}")
21
+
22
+ val callData = request.extras?.getString(EXTRA_CALL_DATA) ?: ""
23
+ val connection = MyConnection(applicationContext, callData)
24
+ connection.setRinging()
25
+ return connection
26
+ }
27
+
28
+ override fun onCreateOutgoingConnection(
29
+ phoneAccountHandle: PhoneAccountHandle,
30
+ request: ConnectionRequest
31
+ ): Connection {
32
+ Log.d(TAG, "onCreateOutgoingConnection: ${request.extras}")
33
+ val callData = request.extras?.getString(EXTRA_CALL_DATA) ?: ""
34
+ val connection = MyConnection(applicationContext, callData)
35
+ connection.setDialing()
36
+ return connection
37
+ }
38
+ }
@@ -0,0 +1,5 @@
1
+ #import <CallManagerSpec/CallManagerSpec.h>
2
+
3
+ @interface CallManager : NSObject <NativeCallManagerSpec>
4
+
5
+ @end
@@ -0,0 +1,18 @@
1
+ #import "CallManager.h"
2
+
3
+ @implementation CallManager
4
+ RCT_EXPORT_MODULE()
5
+
6
+ - (NSNumber *)multiply:(double)a b:(double)b {
7
+ NSNumber *result = @(a * b);
8
+
9
+ return result;
10
+ }
11
+
12
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
13
+ (const facebook::react::ObjCTurboModule::InitParams &)params
14
+ {
15
+ return std::make_shared<facebook::react::NativeCallManagerSpecJSI>(params);
16
+ }
17
+
18
+ @end
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('CallManager');
5
+ //# sourceMappingURL=NativeCallManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"..\\..\\src","sources":["NativeCallManager.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAWlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,aAAa,CAAC","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import CallManager from "./NativeCallManager.js";
4
+ export default CallManager;
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["CallManager"],"sourceRoot":"..\\..\\src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,WAAW,MAAM,wBAAqB;AAE7C,eAAeA,WAAW","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,12 @@
1
+ import type { TurboModule } from 'react-native';
2
+ export interface Spec extends TurboModule {
3
+ reportIncomingCall(callId: string, callData: string): void;
4
+ endCall(callId: string): void;
5
+ answerCall(callId: string): void;
6
+ silenceRingtone(): void;
7
+ rejectCurrentAndAnswerNew(callId: string, callData: string): void;
8
+ setEventHandler(callback: (event: string, callData: string) => void): void;
9
+ }
10
+ declare const _default: Spec;
11
+ export default _default;
12
+ //# sourceMappingURL=NativeCallManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeCallManager.d.ts","sourceRoot":"","sources":["../../../src/NativeCallManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,IAAI,IAAI,CAAC;IACxB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAClE,eAAe,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;CAC5E;;AAED,wBAAqE"}
@@ -0,0 +1,3 @@
1
+ import CallManager from './NativeCallManager';
2
+ export default CallManager;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,qBAAqB,CAAC;AAE9C,eAAe,WAAW,CAAC"}
package/package.json ADDED
@@ -0,0 +1,164 @@
1
+ {
2
+ "name": "@qusaieilouti99/call-manager",
3
+ "version": "0.1.0",
4
+ "description": "call manager",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "*.podspec",
22
+ "react-native.config.js",
23
+ "!ios/build",
24
+ "!android/build",
25
+ "!android/gradle",
26
+ "!android/gradlew",
27
+ "!android/gradlew.bat",
28
+ "!android/local.properties",
29
+ "!**/__tests__",
30
+ "!**/__fixtures__",
31
+ "!**/__mocks__",
32
+ "!**/.*"
33
+ ],
34
+ "scripts": {
35
+ "example": "yarn workspace @qusaieilouti99/call-manager-example",
36
+ "test": "jest",
37
+ "typecheck": "tsc",
38
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
39
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
40
+ "prepare": "bob build",
41
+ "release": "release-it --only-version"
42
+ },
43
+ "keywords": [
44
+ "react-native",
45
+ "ios",
46
+ "android"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/qusaieilouti99/call-manager.git"
51
+ },
52
+ "author": "Qusai Eilouti ( Script ) <qeilouti@scriptechn.com> (https://github.com/qusaieilouti99)",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/qusaieilouti99/call-manager/issues"
56
+ },
57
+ "homepage": "https://github.com/qusaieilouti99/call-manager#readme",
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org/"
60
+ },
61
+ "devDependencies": {
62
+ "@commitlint/config-conventional": "^19.6.0",
63
+ "@eslint/compat": "^1.2.7",
64
+ "@eslint/eslintrc": "^3.3.0",
65
+ "@eslint/js": "^9.22.0",
66
+ "@evilmartians/lefthook": "^1.5.0",
67
+ "@react-native-community/cli": "15.0.0-alpha.2",
68
+ "@react-native/babel-preset": "0.79.2",
69
+ "@react-native/eslint-config": "^0.78.0",
70
+ "@release-it/conventional-changelog": "^9.0.2",
71
+ "@types/jest": "^29.5.5",
72
+ "@types/react": "^19.0.0",
73
+ "commitlint": "^19.6.1",
74
+ "del-cli": "^5.1.0",
75
+ "eslint": "^9.22.0",
76
+ "eslint-config-prettier": "^10.1.1",
77
+ "eslint-plugin-prettier": "^5.2.3",
78
+ "jest": "^29.7.0",
79
+ "prettier": "^3.0.3",
80
+ "react": "19.0.0",
81
+ "react-native": "0.79.2",
82
+ "react-native-builder-bob": "^0.40.8",
83
+ "release-it": "^17.10.0",
84
+ "turbo": "^1.10.7",
85
+ "typescript": "^5.8.3"
86
+ },
87
+ "peerDependencies": {
88
+ "react": "*",
89
+ "react-native": "*"
90
+ },
91
+ "workspaces": [
92
+ "example"
93
+ ],
94
+ "packageManager": "yarn@3.6.1",
95
+ "jest": {
96
+ "preset": "react-native",
97
+ "modulePathIgnorePatterns": [
98
+ "<rootDir>/example/node_modules",
99
+ "<rootDir>/lib/"
100
+ ]
101
+ },
102
+ "commitlint": {
103
+ "extends": [
104
+ "@commitlint/config-conventional"
105
+ ]
106
+ },
107
+ "release-it": {
108
+ "git": {
109
+ "commitMessage": "chore: release ${version}",
110
+ "tagName": "v${version}"
111
+ },
112
+ "npm": {
113
+ "publish": true
114
+ },
115
+ "github": {
116
+ "release": true
117
+ },
118
+ "plugins": {
119
+ "@release-it/conventional-changelog": {
120
+ "preset": {
121
+ "name": "angular"
122
+ }
123
+ }
124
+ }
125
+ },
126
+ "prettier": {
127
+ "quoteProps": "consistent",
128
+ "singleQuote": true,
129
+ "tabWidth": 2,
130
+ "trailingComma": "es5",
131
+ "useTabs": false
132
+ },
133
+ "react-native-builder-bob": {
134
+ "source": "src",
135
+ "output": "lib",
136
+ "targets": [
137
+ [
138
+ "module",
139
+ {
140
+ "esm": true
141
+ }
142
+ ],
143
+ [
144
+ "typescript",
145
+ {
146
+ "project": "tsconfig.build.json"
147
+ }
148
+ ]
149
+ ]
150
+ },
151
+ "codegenConfig": {
152
+ "name": "CallManagerSpec",
153
+ "type": "modules",
154
+ "jsSrcsDir": "src",
155
+ "android": {
156
+ "javaPackageName": "com.qusaieilouti99.callmanager"
157
+ }
158
+ },
159
+ "create-react-native-library": {
160
+ "languages": "kotlin-objc",
161
+ "type": "turbo-module",
162
+ "version": "0.51.1"
163
+ }
164
+ }
@@ -0,0 +1,13 @@
1
+ import type { TurboModule } from 'react-native';
2
+ import { TurboModuleRegistry } from 'react-native';
3
+
4
+ export interface Spec extends TurboModule {
5
+ reportIncomingCall(callId: string, callData: string): void;
6
+ endCall(callId: string): void;
7
+ answerCall(callId: string): void;
8
+ silenceRingtone(): void;
9
+ rejectCurrentAndAnswerNew(callId: string, callData: string): void;
10
+ setEventHandler(callback: (event: string, callData: string) => void): void;
11
+ }
12
+
13
+ export default TurboModuleRegistry.getEnforcing<Spec>('CallManager');
package/src/index.tsx ADDED
@@ -0,0 +1,3 @@
1
+ import CallManager from './NativeCallManager';
2
+
3
+ export default CallManager;