@stream-io/react-native-callingx 0.1.0-beta.1 → 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 (45) 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/AudioSessionManager.swift +2 -7
  17. package/ios/CallingxImpl.swift +32 -27
  18. package/package.json +3 -3
  19. package/src/CallingxModule.ts +2 -30
  20. package/src/types.ts +35 -15
  21. package/src/utils/constants.ts +25 -16
  22. package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -33
  23. package/android/bin/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  24. package/android/bin/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  25. package/android/bin/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -4
  26. package/android/bin/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  27. package/android/bin/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -60
  28. package/android/bin/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -33
  29. package/android/bin/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  30. package/android/bin/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  31. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_phone_paused_24.xml +0 -11
  32. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_round_call_24.xml +0 -11
  33. package/android/bin/build/intermediates/packaged_res/debug/packageDebugResources/drawable/ic_user.xml +0 -27
  34. package/android/bin/build/outputs/logs/manifest-merger-debug-report.txt +0 -70
  35. package/android/bin/src/main/java/io/getstream/rn/callingx/CallService.kt +0 -402
  36. package/android/bin/src/main/java/io/getstream/rn/callingx/CallingxModule.kt +0 -644
  37. package/android/bin/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +0 -245
  38. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/CallRepository.kt +0 -163
  39. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/LegacyCallRepository.kt +0 -139
  40. package/android/bin/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +0 -432
  41. package/dist/module/utils/permissions.js +0 -86
  42. package/dist/module/utils/permissions.js.map +0 -1
  43. package/dist/typescript/src/utils/permissions.d.ts +0 -8
  44. package/dist/typescript/src/utils/permissions.d.ts.map +0 -1
  45. package/src/utils/permissions.ts +0 -111
@@ -1,432 +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.annotation.RequiresApi
10
- import androidx.core.telecom.CallAttributesCompat
11
- import androidx.core.telecom.CallControlResult
12
- import androidx.core.telecom.CallControlScope
13
- import androidx.core.telecom.CallsManager
14
- import io.getstream.rn.callingx.debugLog
15
- import io.getstream.rn.callingx.model.Call
16
- import io.getstream.rn.callingx.model.CallAction
17
- import kotlinx.coroutines.Job
18
- import kotlinx.coroutines.cancel
19
- import kotlinx.coroutines.channels.Channel
20
- import kotlinx.coroutines.flow.Flow
21
- import kotlinx.coroutines.flow.consumeAsFlow
22
- import kotlinx.coroutines.flow.launchIn
23
- import kotlinx.coroutines.flow.onEach
24
- import kotlinx.coroutines.flow.scan
25
- import kotlinx.coroutines.launch
26
- import kotlinx.coroutines.sync.withLock
27
-
28
- /**
29
- * The central repository that keeps track of the current call and allows to register new calls.
30
- *
31
- * This class contains the main logic to integrate with Telecom SDK.
32
- *
33
- * @see registerCall
34
- */
35
- @RequiresApi(Build.VERSION_CODES.O)
36
- class TelecomCallRepository(context: Context) : CallRepository(context) {
37
-
38
- companion object {
39
- private const val TAG = "[Callingx] TelecomCallRepository"
40
- }
41
-
42
- private var observeCallStateJob: Job? = null
43
-
44
- private val callsManager: CallsManager
45
- private var isSelfAnswered = false
46
- private var isSelfDisconnected = false
47
-
48
- init {
49
- val capabilities =
50
- CallsManager.CAPABILITY_SUPPORTS_CALL_STREAMING or
51
- CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING
52
- callsManager =
53
- CallsManager(context.applicationContext).apply {
54
- registerAppWithTelecom(capabilities)
55
- }
56
- debugLog(TAG, "[repository] init: CallsManager created and registered")
57
- }
58
-
59
- override fun getTag(): String = TAG
60
-
61
- override fun setListener(listener: Listener?) {
62
- this._listener = listener
63
-
64
- observeCallStateJob?.cancel()
65
- observeCallStateJob = observeCallState()
66
- }
67
-
68
- override fun release() {
69
- val currentCall = currentCall.value
70
- if (currentCall is Call.Registered) {
71
- currentCall.processAction(CallAction.Disconnect(DisconnectCause(DisconnectCause.LOCAL)))
72
- }
73
- _currentCall.value = Call.None
74
-
75
- observeCallStateJob?.cancel()
76
- observeCallStateJob = null
77
- _listener = null
78
-
79
- scope.cancel()
80
- }
81
-
82
- /**
83
- * Register a new call with the provided attributes. Use the [currentCall] StateFlow to receive
84
- * status updates and process call related actions.
85
- */
86
- override suspend fun registerCall(
87
- callId: String,
88
- displayName: String,
89
- address: Uri,
90
- isIncoming: Boolean,
91
- isVideo: Boolean,
92
- displayOptions: Bundle?,
93
- ) {
94
- debugLog(
95
- TAG,
96
- "[repository] registerCall: Starting registration - Name: $displayName, Address: $address, Incoming: $isIncoming"
97
- )
98
-
99
- registrationMutex.withLock {
100
- // For simplicity we don't support multiple calls
101
- if (_currentCall.value is Call.Registered) {
102
- Log.w(
103
- TAG,
104
- "[repository] registerCall: Call already registered, ignoring new call request"
105
- )
106
- return@withLock
107
- }
108
- debugLog(
109
- TAG,
110
- "[repository] registerCall: No existing call found, proceeding with registration"
111
- )
112
-
113
- val attributes = createCallAttributes(displayName, address, isIncoming, isVideo)
114
- val actionSource = Channel<CallAction>()
115
-
116
- // Register the call and handle actions in the scope
117
- try {
118
- callsManager.addCall(
119
- attributes,
120
- onIsCallAnswered, // Watch needs to know if it can answer the call
121
- onIsCallDisconnected,
122
- onIsCallActive,
123
- onIsCallInactive
124
- ) {
125
- debugLog(
126
- TAG,
127
- "[repository] registerCall: Inside call scope, setting up call handlers"
128
- )
129
-
130
- // Consume the actions to interact with the call inside the scope
131
- launch { processCallActions(actionSource.consumeAsFlow()) }
132
-
133
- // Update the state to registered with default values while waiting for Telecom
134
- // updates
135
- debugLog(
136
- TAG,
137
- "[repository] registerCall: Creating Registered call state with ID: $callId"
138
- )
139
- _currentCall.value =
140
- Call.Registered(
141
- id = callId,
142
- isActive = false, // can we just register the call as active?
143
- isOnHold = false,
144
- callAttributes = attributes,
145
- displayOptions = displayOptions,
146
- isMuted = false,
147
- errorCode = null,
148
- currentCallEndpoint = null,
149
- availableCallEndpoints = emptyList(),
150
- actionSource = actionSource,
151
- )
152
- debugLog(TAG, "[repository] registerCall: Call state updated to Registered")
153
-
154
- launch {
155
- currentCallEndpoint.collect {
156
- updateCurrentCall { copy(currentCallEndpoint = it) }
157
- }
158
- }
159
- launch {
160
- availableEndpoints.collect {
161
- updateCurrentCall { copy(availableCallEndpoints = it) }
162
- }
163
- }
164
- launch { isMuted.collect { updateCurrentCall { copy(isMuted = it) } } }
165
- }
166
- debugLog(
167
- TAG,
168
- "[repository] registerCall: Call successfully registered with Telecom SDK"
169
- )
170
- } catch (e: Exception) {
171
- Log.e(TAG, "[repository] registerCall: Error registering call", e)
172
- throw e
173
- } finally {
174
- debugLog(TAG, "[repository] registerCall: Call scope ended, setting state to None")
175
- _currentCall.value = Call.None
176
- }
177
- }
178
- }
179
-
180
- override fun updateCall(
181
- callId: String,
182
- displayName: String,
183
- address: Uri,
184
- isVideo: Boolean,
185
- displayOptions: Bundle?,
186
- ) {
187
- debugLog(
188
- TAG,
189
- "[repository] updateCall: Starting update - Name: $displayName, Address: $address, IsVideo: $isVideo"
190
- )
191
- super.updateCall(callId, displayName, address, isVideo, displayOptions)
192
- }
193
-
194
- private fun observeCallState(): Job {
195
- return currentCall
196
- .scan(Pair<Call?, Call>(null, currentCall.value)) { (_, prev), next ->
197
- Pair(prev, next)
198
- }
199
- .onEach { (previous, current) ->
200
- when {
201
- previous is Call.None && current is Call.Registered -> {
202
- _listener?.onCallRegistered(current.id, current.isIncoming())
203
- }
204
- previous is Call.Registered && current is Call.Registered -> {
205
- if (previous.isMuted != current.isMuted) {
206
- debugLog(
207
- TAG,
208
- "[repository] observeCallState: Mute changed: ${current.isMuted}"
209
- )
210
- _listener?.onMuteCallChanged(current.id, current.isMuted)
211
- }
212
- if (previous.currentCallEndpoint != current.currentCallEndpoint) {
213
- current.currentCallEndpoint?.let {
214
- _listener?.onCallEndpointChanged(current.id, it.name.toString())
215
- }
216
- }
217
- }
218
- }
219
- _listener?.onCallStateChanged(current)
220
- }
221
- .launchIn(scope)
222
- }
223
-
224
- /** Collect the action source to handle client actions inside the call scope */
225
- private suspend fun CallControlScope.processCallActions(actionSource: Flow<CallAction>) {
226
- actionSource.collect { action ->
227
- debugLog(TAG, "[repository] processCallActions: action: ${action::class.simpleName}")
228
- when (action) {
229
- is CallAction.Answer -> {
230
- doAnswer(action.isAudioCall)
231
- }
232
- is CallAction.Disconnect -> {
233
- doDisconnect(action)
234
- }
235
- is CallAction.SwitchAudioEndpoint -> {
236
- doSwitchEndpoint(action)
237
- }
238
- is CallAction.TransferCall -> {
239
- debugLog(
240
- TAG,
241
- "[repository] processCallActions: Transfer to endpoint: ${action.endpointId}"
242
- )
243
- val call = _currentCall.value as? Call.Registered
244
- val endpoints =
245
- call?.availableCallEndpoints?.firstOrNull {
246
- it.identifier == action.endpointId
247
- }
248
- if (endpoints != null) {
249
- requestEndpointChange(
250
- endpoint = endpoints,
251
- )
252
- } else {
253
- Log.w(
254
- TAG,
255
- "[repository] processCallActions: Endpoint not found for transfer, ignoring"
256
- )
257
- }
258
- }
259
- CallAction.Hold -> {
260
- when (val result = setInactive()) {
261
- is CallControlResult.Success -> {
262
- onIsCallInactive()
263
- }
264
- is CallControlResult.Error -> {
265
- Log.e(
266
- TAG,
267
- "[repository] processCallActions: Hold action failed with error code: ${result.errorCode}"
268
- )
269
- updateCurrentCall { copy(errorCode = result.errorCode) }
270
- }
271
- }
272
- }
273
- CallAction.Activate -> {
274
- when (val result = setActive()) {
275
- is CallControlResult.Success -> {
276
- onIsCallActive()
277
- }
278
- is CallControlResult.Error -> {
279
- Log.e(
280
- TAG,
281
- "[repository] processCallActions: Activate action failed with error code: ${result.errorCode}"
282
- )
283
- updateCurrentCall { copy(errorCode = result.errorCode) }
284
- }
285
- }
286
- }
287
- is CallAction.ToggleMute -> {
288
- // We cannot programmatically mute the telecom stack. Instead we just update
289
- // the state of the call and this will start/stop audio capturing.
290
- debugLog(TAG, "[repository] processCallActions: Toggling mute: ${action.isMute}")
291
- updateCurrentCall {
292
- val newMutedState = action.isMute
293
- copy(isMuted = newMutedState)
294
- }
295
- }
296
- }
297
- }
298
- debugLog(TAG, "[repository] processCallActions: Action collection ended")
299
- }
300
-
301
-
302
- private suspend fun CallControlScope.doSwitchEndpoint(action: CallAction.SwitchAudioEndpoint) {
303
- debugLog(TAG, "[repository] doSwitchEndpoint: Switching to endpoint: ${action.endpointId}")
304
- if (_currentCall.value !is Call.Registered) {
305
- Log.w(TAG, "[repository] doSwitchEndpoint: Call not registered, ignoring")
306
- return
307
- }
308
- // TODO once availableCallEndpoints is a state flow we can just get the value
309
- val endpoints = (_currentCall.value as Call.Registered).availableCallEndpoints
310
- // Switch to the given endpoint or fallback to the best possible one.
311
- val newEndpoint = endpoints.firstOrNull { it.identifier == action.endpointId }
312
-
313
- if (newEndpoint != null) {
314
- debugLog(
315
- TAG,
316
- "[repository] doSwitchEndpoint: Found endpoint: ${newEndpoint.name}, requesting change"
317
- )
318
- requestEndpointChange(newEndpoint).also {
319
- debugLog(TAG, "[repository] doSwitchEndpoint: Endpoint change result: $it")
320
- }
321
- } else {
322
- Log.w(TAG, "[repository] doSwitchEndpoint: Endpoint not found in available endpoints")
323
- }
324
- }
325
-
326
- private suspend fun CallControlScope.doDisconnect(action: CallAction.Disconnect) {
327
- isSelfDisconnected = true
328
- debugLog(TAG, "[repository] doDisconnect: Disconnecting call with cause: ${action.cause}")
329
- disconnect(action.cause)
330
- debugLog(TAG, "[repository] doDisconnect: Disconnect called, triggering onIsCallDisconnected")
331
- onIsCallDisconnected(action.cause)
332
- }
333
-
334
- private suspend fun CallControlScope.doAnswer(isAudioCall: Boolean) {
335
- isSelfAnswered = true
336
- val callType =
337
- if (isAudioCall) CallAttributesCompat.CALL_TYPE_AUDIO_CALL
338
- else CallAttributesCompat.CALL_TYPE_VIDEO_CALL
339
-
340
- when (val result = answer(callType)) {
341
- is CallControlResult.Success -> {
342
- onIsCallAnswered(callType)
343
- }
344
- is CallControlResult.Error -> {
345
- Log.e(
346
- TAG,
347
- "[repository] doAnswer: Answer failed with error code: ${result.errorCode}"
348
- )
349
- isSelfAnswered = false
350
- updateCurrentCall {
351
- Call.Unregistered(
352
- id = id,
353
- callAttributes = callAttributes,
354
- disconnectCause = DisconnectCause(DisconnectCause.BUSY),
355
- )
356
- }
357
- }
358
- }
359
- }
360
-
361
- /**
362
- * Can the call be successfully answered?? TIP: We would check the connection/call state to see
363
- * if we can answer a call Example you may need to wait for another call to hold.
364
- */
365
- val onIsCallAnswered: suspend (type: Int) -> Unit = {
366
- debugLog(
367
- TAG,
368
- "[repository] onIsCallAnswered: Call answered, type: $it, isSelfAnswered: $isSelfAnswered"
369
- )
370
- updateCurrentCall { copy(isActive = true, isOnHold = false) }
371
-
372
- val call = _currentCall.value
373
- val source = if (isSelfAnswered) EventSource.APP else EventSource.SYS
374
- if (call is Call.Registered) {
375
- _listener?.onIsCallAnswered(call.id, source)
376
- }
377
- isSelfAnswered = false
378
- debugLog(TAG, "[repository] onIsCallAnswered: Call state updated to active")
379
- }
380
-
381
- /** Can the call perform a disconnect */
382
- val onIsCallDisconnected: suspend (cause: DisconnectCause) -> Unit = {
383
- debugLog(
384
- TAG,
385
- "[repository] onIsCallDisconnected: Call disconnected, cause: ${it.reason}, description: ${it.description}"
386
- )
387
- val source = if (isSelfDisconnected) EventSource.APP else EventSource.SYS
388
- var callId: String? = null
389
- if (_currentCall.value is Call.Registered) {
390
- callId = (_currentCall.value as Call.Registered).id
391
- }
392
-
393
- updateCurrentCall { Call.Unregistered(id, callAttributes, it) }
394
- _listener?.onIsCallDisconnected(callId, it, source)
395
- isSelfDisconnected = false
396
- debugLog(TAG, "[repository] onIsCallDisconnected: Call state updated to Unregistered")
397
- }
398
-
399
- /**
400
- * Check is see if we can make the call active. Other calls and state might stop us from
401
- * activating the call
402
- */
403
- val onIsCallActive: suspend () -> Unit = {
404
- debugLog(TAG, "[repository] onIsCallActive: Call became active")
405
- updateCurrentCall {
406
- copy(
407
- errorCode = null,
408
- isActive = true,
409
- isOnHold = false,
410
- )
411
- }
412
-
413
- val call = _currentCall.value
414
- if (call is Call.Registered) {
415
- _listener?.onIsCallActive(call.id)
416
- }
417
- debugLog(TAG, "[repository] onIsCallActive: Call state updated")
418
- }
419
-
420
- /** Check to see if we can make the call inactivate */
421
- val onIsCallInactive: suspend () -> Unit = {
422
- debugLog(TAG, "[repository] onIsCallInactive: Call became inactive (on hold)")
423
- updateCurrentCall { copy(errorCode = null, isOnHold = true) }
424
-
425
- val call = _currentCall.value
426
- if (call is Call.Registered) {
427
- _listener?.onIsCallInactive(call.id)
428
- }
429
- debugLog(TAG, "[repository] onIsCallInactive: Call state updated to on hold")
430
- }
431
-
432
- }
@@ -1,86 +0,0 @@
1
- "use strict";
2
-
3
- import { Platform, PermissionsAndroid } from 'react-native';
4
- const allowedPostNotifications = Platform.OS === 'android' && Platform.Version < 33;
5
- export const requestCallPermissions = async () => {
6
- if (Platform.OS !== 'android') {
7
- return {
8
- recordAudio: true,
9
- postNotifications: true
10
- }; // iOS handles permissions differently
11
- }
12
- const permissions = [PermissionsAndroid.PERMISSIONS.RECORD_AUDIO];
13
-
14
- // Add POST_NOTIFICATIONS for Android 13+
15
- if (Platform.Version >= 33) {
16
- permissions.push(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
17
- }
18
-
19
- // Add WRITE_CALL_LOG for call history (required for Android 6.0+)
20
- // Note: PermissionsAndroid doesn't have a constant for this, so we use the string directly
21
- // permissions.push('android.permission.WRITE_CALL_LOG');
22
-
23
- try {
24
- const results = await PermissionsAndroid.requestMultiple(permissions);
25
-
26
- // Check if all permissions are granted
27
- const allGranted = Object.values(results).every(status => status === PermissionsAndroid.RESULTS.GRANTED);
28
- if (!allGranted) {
29
- const deniedPermissions = Object.entries(results).filter(([, status]) => status !== PermissionsAndroid.RESULTS.GRANTED).map(([permission]) => permission);
30
- console.warn('Denied permissions:', deniedPermissions);
31
- }
32
- return {
33
- recordAudio: results[PermissionsAndroid.PERMISSIONS.RECORD_AUDIO] === PermissionsAndroid.RESULTS.GRANTED,
34
- postNotifications: results[PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS] === PermissionsAndroid.RESULTS.GRANTED || allowedPostNotifications
35
- };
36
- } catch (err) {
37
- console.warn('Error requesting permissions:', err);
38
- return {
39
- recordAudio: false,
40
- postNotifications: false
41
- };
42
- }
43
- };
44
- export const checkCallPermissions = async () => {
45
- if (Platform.OS !== 'android') {
46
- return {
47
- recordAudio: true,
48
- postNotifications: true
49
- };
50
- }
51
- const permissions = [PermissionsAndroid.PERMISSIONS.RECORD_AUDIO];
52
- if (Platform.Version >= 33) {
53
- permissions.push(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
54
- }
55
- try {
56
- const results = await Promise.all(permissions.map(async permission => ({
57
- permission,
58
- granted: await PermissionsAndroid.check(permission)
59
- })));
60
- const resultsObject = results.reduce((acc, {
61
- permission,
62
- granted
63
- }) => {
64
- acc[permission] = granted;
65
- return acc;
66
- }, {});
67
- return {
68
- recordAudio: resultsObject[PermissionsAndroid.PERMISSIONS.RECORD_AUDIO],
69
- postNotifications: resultsObject[PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS] || allowedPostNotifications
70
- };
71
- } catch (err) {
72
- console.warn('Error checking permissions:', err);
73
- return {
74
- recordAudio: false,
75
- postNotifications: false
76
- };
77
- }
78
- };
79
- export const requestPostNotificationPermissions = async () => {
80
- if (Platform.OS !== 'android') {
81
- return true;
82
- }
83
- const results = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
84
- return results === PermissionsAndroid.RESULTS.GRANTED || allowedPostNotifications;
85
- };
86
- //# sourceMappingURL=permissions.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["Platform","PermissionsAndroid","allowedPostNotifications","OS","Version","requestCallPermissions","recordAudio","postNotifications","permissions","PERMISSIONS","RECORD_AUDIO","push","POST_NOTIFICATIONS","results","requestMultiple","allGranted","Object","values","every","status","RESULTS","GRANTED","deniedPermissions","entries","filter","map","permission","console","warn","err","checkCallPermissions","Promise","all","granted","check","resultsObject","reduce","acc","requestPostNotificationPermissions","request"],"sourceRoot":"../../../src","sources":["utils/permissions.ts"],"mappings":";;AAAA,SAASA,QAAQ,EAAEC,kBAAkB,QAAQ,cAAc;AAQ3D,MAAMC,wBAAwB,GAC5BF,QAAQ,CAACG,EAAE,KAAK,SAAS,IAAIH,QAAQ,CAACI,OAAO,GAAG,EAAE;AAEpD,OAAO,MAAMC,sBAAsB,GAAG,MAAAA,CAAA,KAAwC;EAC5E,IAAIL,QAAQ,CAACG,EAAE,KAAK,SAAS,EAAE;IAC7B,OAAO;MAAEG,WAAW,EAAE,IAAI;MAAEC,iBAAiB,EAAE;IAAK,CAAC,CAAC,CAAC;EACzD;EAEA,MAAMC,WAAqB,GAAG,CAACP,kBAAkB,CAACQ,WAAW,CAACC,YAAY,CAAC;;EAE3E;EACA,IAAIV,QAAQ,CAACI,OAAO,IAAI,EAAE,EAAE;IAC1BI,WAAW,CAACG,IAAI,CAACV,kBAAkB,CAACQ,WAAW,CAACG,kBAAkB,CAAC;EACrE;;EAEA;EACA;EACA;;EAEA,IAAI;IACF,MAAMC,OAAO,GAAG,MAAMZ,kBAAkB,CAACa,eAAe,CACtDN,WACF,CAAC;;IAED;IACA,MAAMO,UAAU,GAAGC,MAAM,CAACC,MAAM,CAACJ,OAAO,CAAC,CAACK,KAAK,CAC5CC,MAAM,IAAKA,MAAM,KAAKlB,kBAAkB,CAACmB,OAAO,CAACC,OACpD,CAAC;IAED,IAAI,CAACN,UAAU,EAAE;MACf,MAAMO,iBAAiB,GAAGN,MAAM,CAACO,OAAO,CAACV,OAAO,CAAC,CAC9CW,MAAM,CAAC,CAAC,GAAGL,MAAM,CAAC,KAAKA,MAAM,KAAKlB,kBAAkB,CAACmB,OAAO,CAACC,OAAO,CAAC,CACrEI,GAAG,CAAC,CAAC,CAACC,UAAU,CAAC,KAAKA,UAAU,CAAC;MAEpCC,OAAO,CAACC,IAAI,CAAC,qBAAqB,EAAEN,iBAAiB,CAAC;IACxD;IAEA,OAAO;MACLhB,WAAW,EACTO,OAAO,CAACZ,kBAAkB,CAACQ,WAAW,CAACC,YAAY,CAAC,KACpDT,kBAAkB,CAACmB,OAAO,CAACC,OAAO;MACpCd,iBAAiB,EACfM,OAAO,CAACZ,kBAAkB,CAACQ,WAAW,CAACG,kBAAkB,CAAC,KACxDX,kBAAkB,CAACmB,OAAO,CAACC,OAAO,IAAInB;IAC5C,CAAC;EACH,CAAC,CAAC,OAAO2B,GAAG,EAAE;IACZF,OAAO,CAACC,IAAI,CAAC,+BAA+B,EAAEC,GAAG,CAAC;IAClD,OAAO;MAAEvB,WAAW,EAAE,KAAK;MAAEC,iBAAiB,EAAE;IAAM,CAAC;EACzD;AACF,CAAC;AAED,OAAO,MAAMuB,oBAAoB,GAAG,MAAAA,CAAA,KAAwC;EAC1E,IAAI9B,QAAQ,CAACG,EAAE,KAAK,SAAS,EAAE;IAC7B,OAAO;MAAEG,WAAW,EAAE,IAAI;MAAEC,iBAAiB,EAAE;IAAK,CAAC;EACvD;EAEA,MAAMC,WAAqB,GAAG,CAACP,kBAAkB,CAACQ,WAAW,CAACC,YAAY,CAAC;EAE3E,IAAIV,QAAQ,CAACI,OAAO,IAAI,EAAE,EAAE;IAC1BI,WAAW,CAACG,IAAI,CAACV,kBAAkB,CAACQ,WAAW,CAACG,kBAAkB,CAAC;EACrE;EAEA,IAAI;IACF,MAAMC,OAAO,GAAG,MAAMkB,OAAO,CAACC,GAAG,CAC/BxB,WAAW,CAACiB,GAAG,CAAC,MAAOC,UAAU,KAAM;MACrCA,UAAU;MACVO,OAAO,EAAE,MAAMhC,kBAAkB,CAACiC,KAAK,CAACR,UAAwB;IAClE,CAAC,CAAC,CACJ,CAAC;IAED,MAAMS,aAAa,GAAGtB,OAAO,CAACuB,MAAM,CAClC,CAACC,GAAG,EAAE;MAAEX,UAAU;MAAEO;IAAQ,CAAC,KAAK;MAChCI,GAAG,CAACX,UAAU,CAAe,GAAGO,OAAO;MACvC,OAAOI,GAAG;IACZ,CAAC,EACD,CAAC,CACH,CAAC;IAED,OAAO;MACL/B,WAAW,EAAE6B,aAAa,CAAClC,kBAAkB,CAACQ,WAAW,CAACC,YAAY,CAAC;MACvEH,iBAAiB,EACf4B,aAAa,CAAClC,kBAAkB,CAACQ,WAAW,CAACG,kBAAkB,CAAC,IAChEV;IACJ,CAAC;EACH,CAAC,CAAC,OAAO2B,GAAG,EAAE;IACZF,OAAO,CAACC,IAAI,CAAC,6BAA6B,EAAEC,GAAG,CAAC;IAChD,OAAO;MAAEvB,WAAW,EAAE,KAAK;MAAEC,iBAAiB,EAAE;IAAM,CAAC;EACzD;AACF,CAAC;AAED,OAAO,MAAM+B,kCAAkC,GAC7C,MAAAA,CAAA,KAA8B;EAC5B,IAAItC,QAAQ,CAACG,EAAE,KAAK,SAAS,EAAE;IAC7B,OAAO,IAAI;EACb;EAEA,MAAMU,OAAO,GAAG,MAAMZ,kBAAkB,CAACsC,OAAO,CAC9CtC,kBAAkB,CAACQ,WAAW,CAACG,kBACjC,CAAC;EACD,OACEC,OAAO,KAAKZ,kBAAkB,CAACmB,OAAO,CAACC,OAAO,IAAInB,wBAAwB;AAE9E,CAAC","ignoreList":[]}
@@ -1,8 +0,0 @@
1
- export interface PermissionsResult {
2
- recordAudio: boolean;
3
- postNotifications: boolean;
4
- }
5
- export declare const requestCallPermissions: () => Promise<PermissionsResult>;
6
- export declare const checkCallPermissions: () => Promise<PermissionsResult>;
7
- export declare const requestPostNotificationPermissions: () => Promise<boolean>;
8
- //# sourceMappingURL=permissions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/utils/permissions.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAKD,eAAO,MAAM,sBAAsB,QAAa,OAAO,CAAC,iBAAiB,CA8CxE,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAa,OAAO,CAAC,iBAAiB,CAqCtE,CAAC;AAEF,eAAO,MAAM,kCAAkC,QACnC,OAAO,CAAC,OAAO,CAWxB,CAAC"}
@@ -1,111 +0,0 @@
1
- import { Platform, PermissionsAndroid } from 'react-native';
2
- import type { Permission } from 'react-native';
3
-
4
- export interface PermissionsResult {
5
- recordAudio: boolean;
6
- postNotifications: boolean;
7
- }
8
-
9
- const allowedPostNotifications =
10
- Platform.OS === 'android' && Platform.Version < 33;
11
-
12
- export const requestCallPermissions = async (): Promise<PermissionsResult> => {
13
- if (Platform.OS !== 'android') {
14
- return { recordAudio: true, postNotifications: true }; // iOS handles permissions differently
15
- }
16
-
17
- const permissions: string[] = [PermissionsAndroid.PERMISSIONS.RECORD_AUDIO];
18
-
19
- // Add POST_NOTIFICATIONS for Android 13+
20
- if (Platform.Version >= 33) {
21
- permissions.push(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
22
- }
23
-
24
- // Add WRITE_CALL_LOG for call history (required for Android 6.0+)
25
- // Note: PermissionsAndroid doesn't have a constant for this, so we use the string directly
26
- // permissions.push('android.permission.WRITE_CALL_LOG');
27
-
28
- try {
29
- const results = await PermissionsAndroid.requestMultiple(
30
- permissions as Permission[],
31
- );
32
-
33
- // Check if all permissions are granted
34
- const allGranted = Object.values(results).every(
35
- (status) => status === PermissionsAndroid.RESULTS.GRANTED,
36
- );
37
-
38
- if (!allGranted) {
39
- const deniedPermissions = Object.entries(results)
40
- .filter(([, status]) => status !== PermissionsAndroid.RESULTS.GRANTED)
41
- .map(([permission]) => permission);
42
-
43
- console.warn('Denied permissions:', deniedPermissions);
44
- }
45
-
46
- return {
47
- recordAudio:
48
- results[PermissionsAndroid.PERMISSIONS.RECORD_AUDIO] ===
49
- PermissionsAndroid.RESULTS.GRANTED,
50
- postNotifications:
51
- results[PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS] ===
52
- PermissionsAndroid.RESULTS.GRANTED || allowedPostNotifications,
53
- };
54
- } catch (err) {
55
- console.warn('Error requesting permissions:', err);
56
- return { recordAudio: false, postNotifications: false };
57
- }
58
- };
59
-
60
- export const checkCallPermissions = async (): Promise<PermissionsResult> => {
61
- if (Platform.OS !== 'android') {
62
- return { recordAudio: true, postNotifications: true };
63
- }
64
-
65
- const permissions: string[] = [PermissionsAndroid.PERMISSIONS.RECORD_AUDIO];
66
-
67
- if (Platform.Version >= 33) {
68
- permissions.push(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
69
- }
70
-
71
- try {
72
- const results = await Promise.all(
73
- permissions.map(async (permission) => ({
74
- permission,
75
- granted: await PermissionsAndroid.check(permission as Permission),
76
- })),
77
- );
78
-
79
- const resultsObject = results.reduce(
80
- (acc, { permission, granted }) => {
81
- acc[permission as Permission] = granted;
82
- return acc;
83
- },
84
- {} as { [key in Permission]: boolean },
85
- );
86
-
87
- return {
88
- recordAudio: resultsObject[PermissionsAndroid.PERMISSIONS.RECORD_AUDIO],
89
- postNotifications:
90
- resultsObject[PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS] ||
91
- allowedPostNotifications,
92
- };
93
- } catch (err) {
94
- console.warn('Error checking permissions:', err);
95
- return { recordAudio: false, postNotifications: false };
96
- }
97
- };
98
-
99
- export const requestPostNotificationPermissions =
100
- async (): Promise<boolean> => {
101
- if (Platform.OS !== 'android') {
102
- return true;
103
- }
104
-
105
- const results = await PermissionsAndroid.request(
106
- PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
107
- );
108
- return (
109
- results === PermissionsAndroid.RESULTS.GRANTED || allowedPostNotifications
110
- );
111
- };