@stream-io/react-native-callingx 0.1.0-beta.2 → 0.1.0-beta.3

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 (44) hide show
  1. package/README.md +12 -8
  2. package/android/bin/src/main/AndroidManifest.xml +29 -0
  3. package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/NotificationChannelsManager.kt +104 -0
  4. package/android/src/main/AndroidManifest.xml +0 -1
  5. package/android/src/main/java/io/getstream/rn/callingx/CallingxModule.kt +3 -3
  6. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationChannelsManager.kt +9 -18
  7. package/dist/module/CallingxModule.js +2 -14
  8. package/dist/module/CallingxModule.js.map +1 -1
  9. package/dist/module/utils/constants.js +36 -9
  10. package/dist/module/utils/constants.js.map +1 -1
  11. package/dist/typescript/src/CallingxModule.d.ts +1 -5
  12. package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
  13. package/dist/typescript/src/types.d.ts +41 -10
  14. package/dist/typescript/src/types.d.ts.map +1 -1
  15. package/dist/typescript/src/utils/constants.d.ts.map +1 -1
  16. package/ios/CallingxImpl.swift +19 -18
  17. package/package.json +2 -2
  18. package/src/CallingxModule.ts +2 -30
  19. package/src/types.ts +35 -15
  20. package/src/utils/constants.ts +25 -16
  21. package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -33
  22. package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  23. package/android/bin/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  24. package/android/bin/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -4
  25. package/android/bin/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  26. package/android/bin/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -60
  27. package/android/bin/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -33
  28. package/android/bin/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  29. package/android/bin/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  30. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_phone_paused_24.xml +0 -11
  31. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_round_call_24.xml +0 -11
  32. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_user.xml +0 -27
  33. package/android/bin/build/outputs/logs/manifest-merger-debug-report.txt +0 -70
  34. package/android/bin/src/main/java/io/getstream/rn/callingx/CallService.kt +0 -402
  35. package/android/bin/src/main/java/io/getstream/rn/callingx/CallingxModule.kt +0 -644
  36. package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +0 -245
  37. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/CallRepository.kt +0 -163
  38. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/LegacyCallRepository.kt +0 -139
  39. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +0 -432
  40. package/dist/module/utils/permissions.js +0 -86
  41. package/dist/module/utils/permissions.js.map +0 -1
  42. package/dist/typescript/src/utils/permissions.d.ts +0 -8
  43. package/dist/typescript/src/utils/permissions.d.ts.map +0 -1
  44. package/src/utils/permissions.ts +0 -111
@@ -1,245 +0,0 @@
1
- package io.getstream.rn.callingx.notifications
2
-
3
- import android.app.Notification
4
- import android.content.Context
5
- import android.media.Ringtone
6
- import android.media.RingtoneManager
7
- import android.net.Uri
8
- import android.os.Build
9
- import android.telecom.DisconnectCause
10
- import android.util.Log
11
- import androidx.core.app.NotificationCompat
12
- import androidx.core.app.NotificationManagerCompat
13
- import androidx.core.app.Person
14
- import androidx.core.graphics.drawable.IconCompat
15
- import io.getstream.rn.callingx.CallService
16
- import io.getstream.rn.callingx.CallingxModule
17
- import io.getstream.rn.callingx.R
18
- import io.getstream.rn.callingx.ResourceUtils
19
- import io.getstream.rn.callingx.debugLog
20
- import io.getstream.rn.callingx.getDisconnectCauseString
21
- import io.getstream.rn.callingx.model.Call
22
- import io.getstream.rn.callingx.repo.CallRepository
23
-
24
- /**
25
- * Handles call status changes and updates the notification accordingly. For more guidance around
26
- * notifications check https://developer.android.com/develop/ui/views/notifications
27
- *
28
- * @see updateCallNotification
29
- */
30
- class CallNotificationManager(
31
- private val context: Context,
32
- private val notificationManager: NotificationManagerCompat =
33
- NotificationManagerCompat.from(context)
34
- ) {
35
-
36
- internal companion object {
37
- private const val TAG = "[Callingx] CallNotificationManager"
38
-
39
- const val NOTIFICATION_ID = 200
40
- }
41
-
42
- private var notificationsConfig = NotificationsConfig.loadNotificationsConfig(context)
43
-
44
- private var ringtone: Ringtone? = null
45
-
46
- private var hasBecameActive = false
47
-
48
- private var lastPostedSnapshot: NotificationSnapshot? = null
49
-
50
- /**
51
- * Snapshot of call state used to detect notification changes.
52
- * Using a data class ensures immutable comparison and avoids issues
53
- * with mutable Call.Registered objects.
54
- */
55
- private data class NotificationSnapshot(
56
- val id: String,
57
- val isActive: Boolean,
58
- val isIncoming: Boolean,
59
- val displayTitle: String?,
60
- val displaySubtitle: String?,
61
- val displayName: CharSequence,
62
- val address: Uri
63
- )
64
-
65
- private fun Call.Registered.toSnapshot() = NotificationSnapshot(
66
- id = id,
67
- isActive = isActive,
68
- isIncoming = isIncoming(),
69
- displayTitle = displayOptions?.getString(CallService.EXTRA_DISPLAY_TITLE),
70
- displaySubtitle = displayOptions?.getString(CallService.EXTRA_DISPLAY_SUBTITLE),
71
- displayName = callAttributes.displayName,
72
- address = callAttributes.address
73
- )
74
-
75
- fun createNotification(call: Call.Registered): Notification {
76
- debugLog(TAG,"[notifications] createNotification: Creating notification for call ID: ${call.id}")
77
-
78
- val contentIntent =
79
- NotificationIntentFactory.getLaunchActivityIntent(
80
- context,
81
- CallingxModule.CALL_ANSWERED_ACTION,
82
- call.id
83
- )
84
- val callStyle = createCallStyle(call)
85
- val channelId = getChannelId(call)
86
- debugLog(TAG, "[notifications] createNotification: Channel ID: $channelId")
87
-
88
- val builder =
89
- NotificationCompat.Builder(context, channelId)
90
- .setContentIntent(contentIntent)
91
- .setFullScreenIntent(contentIntent, true)
92
- .setStyle(callStyle)
93
- .setSmallIcon(R.drawable.ic_round_call_24)
94
- .setCategory(NotificationCompat.CATEGORY_CALL)
95
- .setPriority(NotificationCompat.PRIORITY_MAX)
96
- .setOngoing(true)
97
-
98
- if (!hasBecameActive && call.isActive) {
99
- debugLog(TAG, "[notifications] createNotification: Setting when to current time")
100
- builder.setWhen(System.currentTimeMillis())
101
- builder.setUsesChronometer(true)
102
- builder.setShowWhen(true)
103
- hasBecameActive = true
104
- }
105
-
106
- call.displayOptions?.let {
107
- if (it.containsKey(CallService.EXTRA_DISPLAY_SUBTITLE)) {
108
- builder.setContentText(it.getString(CallService.EXTRA_DISPLAY_SUBTITLE))
109
- }
110
- }
111
-
112
- return builder.build()
113
- }
114
-
115
- fun updateCallNotification(call: Call) {
116
- when (call) {
117
- Call.None, is Call.Unregistered -> {
118
- debugLog(TAG, "[notifications] updateCallNotification: Dismissing notification (call is None or Unregistered)")
119
- notificationManager.cancel(NOTIFICATION_ID)
120
- lastPostedSnapshot = null
121
- hasBecameActive = false
122
- }
123
- is Call.Registered -> {
124
- val newSnapshot = call.toSnapshot()
125
- if (newSnapshot == lastPostedSnapshot) {
126
- debugLog(TAG, "[notifications] updateCallNotification: Skipping - no state change")
127
- return
128
- }
129
-
130
- lastPostedSnapshot = newSnapshot
131
- val notification = createNotification(call)
132
- notificationManager.notify(NOTIFICATION_ID, notification)
133
- debugLog(TAG, "[notifications] updateCallNotification: Notification posted successfully")
134
- }
135
- }
136
- }
137
-
138
- fun cancelNotifications() {
139
- notificationManager.cancel(NOTIFICATION_ID)
140
- hasBecameActive = false
141
- lastPostedSnapshot = null
142
- }
143
-
144
- fun startRingtone() {
145
- if (ringtone?.isPlaying == true) {
146
- debugLog(TAG, "[notifications] startRingtone: Ringtone already playing")
147
- return
148
- }
149
-
150
- try {
151
- val soundUri =
152
- ResourceUtils.getSoundUri(context, notificationsConfig.incomingChannel.sound)
153
- ?: RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
154
-
155
- ringtone = RingtoneManager.getRingtone(context, soundUri)
156
- } catch (e: Exception) {
157
- Log.e(TAG, "[notifications] startRingtone: Error starting ringtone: ${e.message}")
158
- return
159
- }
160
-
161
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
162
- ringtone?.isLooping = true
163
- }
164
-
165
- ringtone?.play()
166
- debugLog(TAG, "[notifications] startRingtone: Ringtone started")
167
- }
168
-
169
- fun stopRingtone() {
170
- if (ringtone?.isPlaying == true) {
171
- ringtone?.stop()
172
- debugLog(TAG, "[notifications] stopRingtone: Ringtone stopped")
173
- }
174
- ringtone = null
175
- }
176
-
177
- private fun getChannelId(call: Call.Registered): String {
178
- return if (call.isIncoming() && !call.isActive) {
179
- notificationsConfig.incomingChannel.id
180
- } else {
181
- notificationsConfig.ongoingChannel.id
182
- }
183
- }
184
-
185
- private fun createCallStyle(call: Call.Registered): NotificationCompat.CallStyle {
186
- val caller = createPerson(call)
187
-
188
- if (call.isIncoming() && !call.isActive) {
189
- return NotificationCompat.CallStyle.forIncomingCall(
190
- caller,
191
- NotificationIntentFactory.getPendingBroadcastIntent(
192
- context,
193
- CallingxModule.CALL_END_ACTION,
194
- call.id
195
- ) {
196
- putExtra(
197
- CallingxModule.EXTRA_DISCONNECT_CAUSE,
198
- getDisconnectCauseString(DisconnectCause(DisconnectCause.REJECTED))
199
- )
200
- putExtra(
201
- CallingxModule.EXTRA_SOURCE,
202
- CallRepository.EventSource.SYS.name.lowercase()
203
- )
204
- },
205
- NotificationIntentFactory.getPendingNotificationIntent(
206
- context,
207
- CallingxModule.CALL_ANSWERED_ACTION,
208
- call.id,
209
- CallRepository.EventSource.SYS.name.lowercase()
210
- )
211
- )
212
- }
213
-
214
- return NotificationCompat.CallStyle.forOngoingCall(
215
- caller,
216
- NotificationIntentFactory.getPendingBroadcastIntent(
217
- context,
218
- CallingxModule.CALL_END_ACTION,
219
- call.id
220
- ) {
221
- putExtra(
222
- CallingxModule.EXTRA_DISCONNECT_CAUSE,
223
- getDisconnectCauseString(DisconnectCause(DisconnectCause.LOCAL))
224
- )
225
- putExtra(
226
- CallingxModule.EXTRA_SOURCE,
227
- CallRepository.EventSource.SYS.name.lowercase()
228
- )
229
- },
230
- )
231
- }
232
-
233
- private fun createPerson(call: Call.Registered): Person {
234
- val displayCallerName = call.displayOptions?.getString(CallService.EXTRA_DISPLAY_TITLE)
235
- val address = call.callAttributes.address.toString()
236
-
237
- return Person.Builder()
238
- .setName(displayCallerName ?: call.callAttributes.displayName)
239
- .setUri(address)
240
- .setIcon(IconCompat.createWithResource(context, R.drawable.ic_user))
241
- .setImportant(true)
242
- .build()
243
- }
244
-
245
- }
@@ -1,163 +0,0 @@
1
- package io.getstream.rn.callingx.repo
2
-
3
- import android.content.Context
4
- import android.net.Uri
5
- import android.os.Build
6
- import android.os.Bundle
7
- import android.telecom.DisconnectCause
8
- import android.util.Log
9
- import androidx.core.telecom.CallAttributesCompat
10
- import io.getstream.rn.callingx.model.Call
11
- import io.getstream.rn.callingx.model.CallAction
12
- import io.getstream.rn.callingx.CallService
13
- import io.getstream.rn.callingx.debugLog
14
- import kotlinx.coroutines.CoroutineScope
15
- import kotlinx.coroutines.Dispatchers
16
- import kotlinx.coroutines.SupervisorJob
17
- import kotlinx.coroutines.cancel
18
- import kotlinx.coroutines.flow.MutableStateFlow
19
- import kotlinx.coroutines.flow.StateFlow
20
- import kotlinx.coroutines.flow.asStateFlow
21
- import kotlinx.coroutines.flow.update
22
- import kotlinx.coroutines.sync.Mutex
23
- import kotlinx.coroutines.channels.Channel
24
-
25
- abstract class CallRepository(protected val context: Context) {
26
-
27
- enum class EventSource {
28
- APP, SYS
29
- }
30
-
31
- interface Listener {
32
- fun onCallStateChanged(call: Call)
33
- fun onIsCallAnswered(callId: String, source: EventSource)
34
- fun onIsCallDisconnected(callId: String?, cause: DisconnectCause, source: EventSource)
35
- fun onIsCallInactive(callId: String)
36
- fun onIsCallActive(callId: String)
37
- fun onCallRegistered(callId: String, incoming: Boolean)
38
- fun onMuteCallChanged(callId: String, isMuted: Boolean)
39
- fun onCallEndpointChanged(callId: String, endpoint: String)
40
- }
41
-
42
- protected val _currentCall: MutableStateFlow<Call> = MutableStateFlow(Call.None)
43
- val currentCall: StateFlow<Call> = _currentCall.asStateFlow()
44
-
45
- protected var _listener: Listener? = null
46
- protected val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
47
- protected val registrationMutex: Mutex = Mutex()
48
-
49
- abstract fun setListener(listener: Listener?)
50
- abstract fun release()
51
-
52
- abstract suspend fun registerCall(
53
- callId: String,
54
- displayName: String,
55
- address: Uri,
56
- isIncoming: Boolean,
57
- isVideo: Boolean,
58
- displayOptions: Bundle?,
59
- )
60
-
61
- open fun updateCall(
62
- callId: String,
63
- displayName: String,
64
- address: Uri,
65
- isVideo: Boolean,
66
- displayOptions: Bundle?,
67
- ) {
68
- updateCurrentCall { copy(displayOptions = displayOptions) }
69
- }
70
-
71
- //this call instance is used to display call notification before the call is registered, this is needed to invoke startForeground method on the service
72
- public fun getTempCall(callInfo: CallService.CallInfo, incoming: Boolean): Call.Registered {
73
- val attributes = createCallAttributes(
74
- displayName = callInfo.name,
75
- address = callInfo.uri,
76
- isIncoming = incoming,
77
- isVideo = callInfo.isVideo
78
- )
79
-
80
- return Call.Registered(
81
- id = callInfo.callId,
82
- isActive = false,
83
- isOnHold = false,
84
- callAttributes = attributes,
85
- displayOptions = callInfo.displayOptions,
86
- isMuted = false,
87
- errorCode = null,
88
- currentCallEndpoint = null,
89
- availableCallEndpoints = emptyList(),
90
- actionSource = Channel<CallAction>() // Temporary channel, will be replaced by actual registration
91
- )
92
- }
93
-
94
- /**
95
- * Update the current state of our call applying the transform lambda only if the call is
96
- * registered. Otherwise keep the current state
97
- */
98
- protected fun updateCurrentCall(transform: Call.Registered.() -> Call) {
99
- val currentState = _currentCall.value
100
- debugLog(
101
- getTag(),
102
- "[repository] updateCurrentCall: Current call state: ${currentState::class.simpleName}"
103
- )
104
-
105
- _currentCall.update { call ->
106
- if (call is Call.Registered) {
107
- val updated = call.transform()
108
- debugLog(
109
- getTag(),
110
- "[repository] updateCurrentCall: Call state updated to: ${updated::class.simpleName}"
111
- )
112
- updated
113
- } else {
114
- Log.w(
115
- getTag(),
116
- "[repository] updateCurrentCall: Call is not Registered, skipping update"
117
- )
118
- call
119
- }
120
- }
121
- }
122
-
123
- protected fun createCallAttributes(
124
- displayName: String,
125
- address: Uri,
126
- isIncoming: Boolean,
127
- isVideo: Boolean
128
- ): CallAttributesCompat {
129
- return CallAttributesCompat(
130
- displayName = displayName,
131
- address = address,
132
- direction =
133
- if (isIncoming) {
134
- CallAttributesCompat.DIRECTION_INCOMING
135
- } else {
136
- CallAttributesCompat.DIRECTION_OUTGOING
137
- },
138
- callType =
139
- if (isVideo) {
140
- CallAttributesCompat.CALL_TYPE_VIDEO_CALL
141
- } else {
142
- CallAttributesCompat.CALL_TYPE_AUDIO_CALL
143
- },
144
- callCapabilities =
145
- CallAttributesCompat.SUPPORTS_SET_INACTIVE or
146
- CallAttributesCompat.SUPPORTS_STREAM or
147
- CallAttributesCompat.SUPPORTS_TRANSFER,
148
- )
149
- }
150
-
151
- protected abstract fun getTag(): String
152
- }
153
-
154
- object CallRepositoryFactory {
155
-
156
- fun create(context: Context): CallRepository {
157
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
158
- TelecomCallRepository(context) // Your current CallRepository renamed
159
- } else {
160
- LegacyCallRepository(context) // Fallback implementation
161
- }
162
- }
163
- }
@@ -1,139 +0,0 @@
1
- package io.getstream.rn.callingx.repo
2
-
3
- import android.content.Context
4
- import android.net.Uri
5
- import android.os.Bundle
6
- import android.util.Log
7
- import androidx.core.telecom.CallAttributesCompat
8
- import io.getstream.rn.callingx.model.Call
9
- import io.getstream.rn.callingx.model.CallAction
10
- import kotlinx.coroutines.cancel
11
- import kotlinx.coroutines.channels.Channel
12
- import kotlinx.coroutines.flow.consumeAsFlow
13
- import kotlinx.coroutines.flow.collect
14
- import kotlinx.coroutines.launch
15
- import kotlinx.coroutines.sync.withLock
16
-
17
- class LegacyCallRepository(context: Context) : CallRepository(context) {
18
-
19
- companion object {
20
- private const val TAG = "[Callingx] LegacyCallRepository"
21
- }
22
-
23
- private var observeCallStateJob: Job? = null
24
-
25
- override fun getTag(): String = TAG
26
-
27
- override fun setListener(listener: Listener?) {
28
- this._listener = listener
29
- // Observe call state changes
30
- observeCallStateJob?.cancel()
31
- observeCallStateJob = scope.launch {
32
- try {
33
- currentCall.collect { _listener?.onCallStateChanged(it) }
34
- } catch (e: Exception) {
35
- Log.e(TAG, "[repository] setListener: Error collecting call state", e)
36
- }
37
- }
38
- }
39
-
40
- override fun release() {
41
- _currentCall.value = Call.None
42
-
43
- observeCallStateJob?.cancel()
44
- observeCallStateJob = null
45
- _listener = null
46
-
47
- scope.cancel()
48
- }
49
-
50
- override suspend fun registerCall(
51
- callId: String,
52
- displayName: String,
53
- address: Uri,
54
- isIncoming: Boolean,
55
- isVideo: Boolean,
56
- displayOptions: Bundle?,
57
- ) {
58
- registrationMutex.withLock {
59
- if (currentCall.value is Call.Registered) {
60
- Log.w(
61
- TAG,
62
- "[repository] registerCall: Call already registered, ignoring new call request"
63
- )
64
- return@withLock
65
- }
66
-
67
- val attributes = createCallAttributes(displayName, address, isIncoming, isVideo)
68
- val actionSource = Channel<CallAction>()
69
-
70
- _currentCall.value =
71
- Call.Registered(
72
- id = callId,
73
- isActive = false,
74
- isOnHold = false,
75
- callAttributes = attributes,
76
- displayOptions = displayOptions,
77
- isMuted = false,
78
- errorCode = null,
79
- currentCallEndpoint = null,
80
- availableCallEndpoints = emptyList(),
81
- actionSource = actionSource,
82
- )
83
-
84
- _listener?.onCallRegistered(callId, isIncoming)
85
-
86
- // Process actions without telecom SDK
87
- scope.launch {
88
- try {
89
- actionSource.consumeAsFlow().collect { action -> processActionLegacy(action) }
90
- } catch (e: Exception) {
91
- Log.e(TAG, "[repository] registerCall: Error consuming actions", e)
92
- }
93
- }
94
- }
95
- }
96
-
97
- override fun updateCall(
98
- callId: String,
99
- displayName: String,
100
- address: Uri,
101
- isVideo: Boolean,
102
- displayOptions: Bundle?,
103
- ) {
104
- super.updateCall(callId, displayName, address, isVideo, displayOptions)
105
- }
106
-
107
- private fun processActionLegacy(action: CallAction) {
108
- when (action) {
109
- is CallAction.Answer -> {
110
- updateCurrentCall { copy(isActive = true, isOnHold = false) }
111
- // In legacy mode, all actions are initiated from the app
112
- (currentCall.value as? Call.Registered)?.let {
113
- _listener?.onIsCallAnswered(it.id, EventSource.APP)
114
- }
115
- }
116
- is CallAction.Disconnect -> {
117
- val call = currentCall.value as? Call.Registered
118
- if (call != null) {
119
- _currentCall.value =
120
- Call.Unregistered(call.id, call.callAttributes, action.cause)
121
- // In legacy mode, all actions are initiated from the app
122
- _listener?.onIsCallDisconnected(
123
- call.id,
124
- action.cause,
125
- EventSource.APP
126
- )
127
- }
128
- }
129
- is CallAction.ToggleMute -> {
130
- updateCurrentCall { copy(isMuted = action.isMute) }
131
- }
132
- // Handle other actions...
133
- else -> {
134
- /* No-op for unsupported actions */
135
- }
136
- }
137
- }
138
-
139
- }