@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.
- package/README.md +12 -8
- package/android/bin/src/main/AndroidManifest.xml +29 -0
- package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/NotificationChannelsManager.kt +104 -0
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/io/getstream/rn/callingx/CallingxModule.kt +3 -3
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationChannelsManager.kt +9 -18
- package/dist/module/CallingxModule.js +2 -14
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/utils/constants.js +36 -9
- package/dist/module/utils/constants.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +1 -5
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +41 -10
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/constants.d.ts.map +1 -1
- package/ios/CallingxImpl.swift +19 -18
- package/package.json +2 -2
- package/src/CallingxModule.ts +2 -30
- package/src/types.ts +35 -15
- package/src/utils/constants.ts +25 -16
- package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -33
- package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
- package/android/bin/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
- package/android/bin/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -4
- package/android/bin/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
- package/android/bin/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -60
- package/android/bin/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -33
- package/android/bin/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
- package/android/bin/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
- package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_phone_paused_24.xml +0 -11
- package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_round_call_24.xml +0 -11
- package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_user.xml +0 -27
- package/android/bin/build/outputs/logs/manifest-merger-debug-report.txt +0 -70
- package/android/bin/src/main/java/io/getstream/rn/callingx/CallService.kt +0 -402
- package/android/bin/src/main/java/io/getstream/rn/callingx/CallingxModule.kt +0 -644
- package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +0 -245
- package/android/bin/src/main/java/io/getstream/rn/callingx/repo/CallRepository.kt +0 -163
- package/android/bin/src/main/java/io/getstream/rn/callingx/repo/LegacyCallRepository.kt +0 -139
- package/android/bin/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +0 -432
- package/dist/module/utils/permissions.js +0 -86
- package/dist/module/utils/permissions.js.map +0 -1
- package/dist/typescript/src/utils/permissions.d.ts +0 -8
- package/dist/typescript/src/utils/permissions.d.ts.map +0 -1
- package/src/utils/permissions.ts +0 -111
package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt
DELETED
|
@@ -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
|
-
}
|