@capgo/capacitor-stream-call 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -206,22 +206,11 @@ fun CallOverlayView(
206
206
  floatingVideoRenderer = floatingVideoRender
207
207
  )
208
208
  } else {
209
- if (connection != RealtimeConnection.Connected) {
210
- android.util.Log.d("CallOverlayView", "Showing waiting message - not connected")
211
- Text(
212
- text = "waiting for a remote participant...",
213
- fontSize = 30.sp,
214
- color = VideoTheme.colors.basePrimary
215
- )
216
- } else {
217
- Text(
218
- modifier = Modifier.padding(30.dp),
219
- text = "Join call ${call.id} in your browser to see the video here",
220
- fontSize = 30.sp,
221
- color = VideoTheme.colors.basePrimary,
222
- textAlign = TextAlign.Center
223
- )
224
- }
209
+ Box(
210
+ modifier = Modifier
211
+ .fillMaxSize()
212
+ .background(VideoTheme.colors.baseSenary)
213
+ )
225
214
  }
226
215
  }
227
216
  }
@@ -35,6 +35,7 @@ import io.getstream.video.android.model.StreamCallId
35
35
  import io.getstream.video.android.model.User
36
36
  import io.getstream.video.android.model.streamCallId
37
37
  import kotlinx.coroutines.DelicateCoroutinesApi
38
+ import kotlinx.coroutines.flow.Flow
38
39
  import kotlinx.coroutines.launch
39
40
  import org.openapitools.client.models.CallAcceptedEvent
40
41
  import org.openapitools.client.models.CallEndedEvent
@@ -42,6 +43,8 @@ import org.openapitools.client.models.CallMissedEvent
42
43
  import org.openapitools.client.models.CallRejectedEvent
43
44
  import org.openapitools.client.models.CallSessionEndedEvent
44
45
  import org.openapitools.client.models.VideoEvent
46
+ import io.getstream.video.android.model.Device
47
+ import kotlinx.coroutines.flow.first
45
48
 
46
49
  // I am not a religious pearson, but at this point, I am not sure even god himself would understand this code
47
50
  // It's a spaghetti-like, tangled, unreadable mess and frankly, I am deeply sorry for the code crimes commited in the Android impl
@@ -60,6 +63,7 @@ public class StreamCallPlugin : Plugin() {
60
63
  private var savedActivity: Activity? = null
61
64
  private var savedActivityPaused = false
62
65
  private var savedCallsToEndOnResume = mutableListOf<Call>()
66
+ private val callStates: MutableMap<String, CallState> = mutableMapOf()
63
67
 
64
68
  private enum class State {
65
69
  NOT_INITIALIZED,
@@ -339,7 +343,7 @@ public class StreamCallPlugin : Plugin() {
339
343
  SecureUserRepository.getInstance(context).save(credentials)
340
344
 
341
345
  // Initialize Stream Video with new credentials
342
- if (!hadSavedCredentials) {
346
+ if (!hadSavedCredentials || (savedCredentials!!.user.id != userId)) {
343
347
  initializeStreamVideo()
344
348
  }
345
349
 
@@ -358,15 +362,20 @@ public class StreamCallPlugin : Plugin() {
358
362
  SecureUserRepository.getInstance(context).removeCurrentUser()
359
363
 
360
364
  // Properly cleanup the client
361
- streamVideoClient?.let {
362
- StreamVideo.removeClient()
363
- }
364
- streamVideoClient = null
365
- state = State.NOT_INITIALIZED
365
+ kotlinx.coroutines.GlobalScope.launch {
366
+ streamVideoClient?.let {
367
+ magicDeviceDelete(it)
368
+ it.logOut()
369
+ StreamVideo.removeClient()
370
+ }
366
371
 
367
- val ret = JSObject()
368
- ret.put("success", true)
369
- call.resolve(ret)
372
+ streamVideoClient = null
373
+ state = State.NOT_INITIALIZED
374
+
375
+ val ret = JSObject()
376
+ ret.put("success", true)
377
+ call.resolve(ret)
378
+ }
370
379
  } catch (e: Exception) {
371
380
  call.reject("Failed to logout", e)
372
381
  }
@@ -552,89 +561,152 @@ public class StreamCallPlugin : Plugin() {
552
561
  client.subscribe { event: VideoEvent ->
553
562
  android.util.Log.v("StreamCallPlugin", "Received an event ${event.getEventType()} $event")
554
563
  when (event) {
555
- is CallEndedEvent -> {
556
- runOnMainThread {
557
- android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallEndedEvent for call ${event.call.cid}")
558
- overlayView?.setContent {
559
- CallOverlayView(
560
- context = context,
561
- streamVideo = streamVideoClient,
562
- call = null
563
- )
564
- }
565
- overlayView?.isVisible = false
566
- // Stop ringtone if it's still playing
567
- ringtonePlayer?.stopRinging()
568
- // Hide incoming call view if visible
569
- incomingCallView?.isVisible = false
570
- }
564
+ // Handle CallCreatedEvent differently - only log it but don't try to access members yet
565
+ is org.openapitools.client.models.CallCreatedEvent -> {
566
+ val callCid = event.call.cid
567
+ android.util.Log.d("StreamCallPlugin", "Call created: $callCid")
568
+
569
+ // let's get the members
570
+ val callParticipants = event.members.filter{ it.user.id != this@StreamCallPlugin.streamVideoClient?.userId } .map { it.user.id }
571
+ android.util.Log.d("StreamCallPlugin", "Call created for $callCid with ${callParticipants.size} participants")
572
+
573
+ // Start tracking this call now that we have the member list
574
+ startCallTimeoutMonitor(callCid, callParticipants)
575
+
571
576
  val data = JSObject().apply {
572
- put("callId", event.call.cid)
573
- put("state", "left")
577
+ put("callId", callCid)
578
+ put("state", "created")
574
579
  }
575
580
  notifyListeners("callEvent", data)
576
581
  }
577
- is CallSessionEndedEvent -> {
578
- runOnMainThread {
579
- android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallSessionEndedEvent for call ${event.call.cid}")
580
- overlayView?.setContent {
581
- CallOverlayView(
582
- context = context,
583
- streamVideo = streamVideoClient,
584
- call = null
585
- )
586
- }
587
- overlayView?.isVisible = false
588
- // Stop ringtone if it's still playing
589
- ringtonePlayer?.stopRinging()
590
- // Hide incoming call view if visible
591
- incomingCallView?.isVisible = false
592
- }
582
+ // Add handler for CallSessionStartedEvent which contains participant information
583
+ is org.openapitools.client.models.CallSessionStartedEvent -> {
584
+ val callCid = event.call.cid
585
+
593
586
  val data = JSObject().apply {
594
- put("callId", event.call.cid)
595
- put("state", "left")
587
+ put("callId", callCid)
588
+ put("state", "session_started")
596
589
  }
597
590
  notifyListeners("callEvent", data)
598
591
  }
592
+
599
593
  is CallRejectedEvent -> {
600
- runOnMainThread {
601
- android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallRejectedEvent for call ${event.call.cid}")
602
- overlayView?.setContent {
603
- CallOverlayView(
604
- context = context,
605
- streamVideo = streamVideoClient,
606
- call = null
607
- )
608
- }
609
- overlayView?.isVisible = false
610
-
611
- val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
612
- if (keyguardManager.isKeyguardLocked) {
613
- android.util.Log.d("StreamCallPlugin", "Stop ringing and move to background")
614
- this@StreamCallPlugin.ringtonePlayer?.stopRinging()
615
- moveAllActivitiesToBackgroundOrKill(context)
616
- }
594
+ val userId = event.user.id
595
+ val callCid = event.call.cid
596
+
597
+ // Update call state
598
+ callStates[callCid]?.let { callState ->
599
+ callState.participantResponses[userId] = "rejected"
617
600
  }
601
+
602
+ // runOnMainThread {
603
+ // android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallRejectedEvent for call ${event.call.cid}")
604
+ // overlayView?.setContent {
605
+ // CallOverlayView(
606
+ // context = context,
607
+ // streamVideo = streamVideoClient,
608
+ // call = null
609
+ // )
610
+ // }
611
+ // overlayView?.isVisible = false
612
+ // }
613
+
618
614
  val data = JSObject().apply {
619
615
  put("callId", event.call.cid)
620
616
  put("state", "rejected")
617
+ put("userId", userId)
621
618
  }
622
619
  notifyListeners("callEvent", data)
620
+
621
+ // Check if all participants have responded
622
+ checkAllParticipantsResponded(callCid)
623
623
  }
624
+
624
625
  is CallMissedEvent -> {
626
+ val userId = event.user.id
627
+ val callCid = event.call.cid
628
+
629
+ // Update call state
630
+ callStates[callCid]?.let { callState ->
631
+ callState.participantResponses[userId] = "missed"
632
+ }
633
+
625
634
  val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
626
635
  if (keyguardManager.isKeyguardLocked) {
627
636
  android.util.Log.d("StreamCallPlugin", "Stop ringing and move to background")
628
637
  this.ringtonePlayer?.stopRinging()
629
638
  moveAllActivitiesToBackgroundOrKill(context)
630
639
  }
640
+
641
+ val data = JSObject().apply {
642
+ put("callId", callCid)
643
+ put("state", "missed")
644
+ put("userId", userId)
645
+ }
646
+ notifyListeners("callEvent", data)
647
+
648
+ // Check if all participants have responded
649
+ checkAllParticipantsResponded(callCid)
650
+ }
651
+
652
+ is CallAcceptedEvent -> {
653
+ val userId = event.user.id
654
+ val callCid = event.call.cid
655
+
656
+ // Update call state
657
+ callStates[callCid]?.let { callState ->
658
+ callState.participantResponses[userId] = "accepted"
659
+
660
+ // Since someone accepted, cancel the timeout timer
661
+ android.util.Log.d("StreamCallPlugin", "Call accepted by $userId, canceling timeout timer for $callCid")
662
+ callState.timer?.removeCallbacksAndMessages(null)
663
+ callState.timer = null
664
+ }
665
+
666
+ val data = JSObject().apply {
667
+ put("callId", callCid)
668
+ put("state", "accepted")
669
+ put("userId", userId)
670
+ }
671
+ notifyListeners("callEvent", data)
672
+ }
673
+
674
+ is CallEndedEvent -> {
675
+ runOnMainThread {
676
+ android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallEndedEvent for call ${event.call.cid}")
677
+ // Clean up call resources
678
+ val callCid = event.call.cid
679
+ cleanupCall(callCid)
680
+ }
681
+ val data = JSObject().apply {
682
+ put("callId", event.call.cid)
683
+ put("state", "left")
684
+ }
685
+ notifyListeners("callEvent", data)
686
+ }
687
+
688
+ is CallSessionEndedEvent -> {
689
+ runOnMainThread {
690
+ android.util.Log.d("StreamCallPlugin", "Setting overlay invisible due to CallSessionEndedEvent for call ${event.call.cid}")
691
+ // Clean up call resources
692
+ val callCid = event.call.cid
693
+ cleanupCall(callCid)
694
+ }
695
+ val data = JSObject().apply {
696
+ put("callId", event.call.cid)
697
+ put("state", "left")
698
+ }
699
+ notifyListeners("callEvent", data)
700
+ }
701
+
702
+ else -> {
703
+ val data = JSObject().apply {
704
+ put("callId", streamVideoClient?.state?.activeCall?.value?.cid)
705
+ put("state", event.getEventType())
706
+ }
707
+ notifyListeners("callEvent", data)
631
708
  }
632
709
  }
633
- val data = JSObject().apply {
634
- put("callId", streamVideoClient?.state?.activeCall?.value?.cid)
635
- put("state", event.getEventType())
636
- }
637
- notifyListeners("callEvent", data)
638
710
  }
639
711
 
640
712
  // Add call state subscription using collect
@@ -1007,6 +1079,7 @@ public class StreamCallPlugin : Plugin() {
1007
1079
  val callType = call.getString("type") ?: "default"
1008
1080
  val shouldRing = call.getBoolean("ring") ?: true
1009
1081
  val callId = java.util.UUID.randomUUID().toString()
1082
+ val callCid = "$callType:$callId"
1010
1083
 
1011
1084
  android.util.Log.d("StreamCallPlugin", "Creating call:")
1012
1085
  android.util.Log.d("StreamCallPlugin", "- Call ID: $callId")
@@ -1019,59 +1092,10 @@ public class StreamCallPlugin : Plugin() {
1019
1092
  try {
1020
1093
  // Create the call object
1021
1094
  val streamCall = streamVideoClient?.call(type = callType, id = callId)
1022
-
1023
- // Track participants responses
1024
- val participantResponses = mutableMapOf<String, String>()
1025
- val totalParticipants = userIds.size
1026
-
1027
- // Subscribe to call events for this specific call
1028
- streamCall?.subscribe { event ->
1029
- when (event) {
1030
- is CallRejectedEvent -> {
1031
- val userId = event.user.id
1032
- android.util.Log.d("StreamCallPlugin", "Call was rejected by user: $userId")
1033
- participantResponses[userId] = "rejected"
1034
-
1035
- val data = JSObject().apply {
1036
- put("callId", callId)
1037
- put("state", "rejected")
1038
- put("userId", userId)
1039
- }
1040
- notifyListeners("callEvent", data)
1041
-
1042
- // Check if all participants have rejected or missed
1043
- checkAllParticipantsResponded(participantResponses, totalParticipants, streamCall)
1044
- }
1045
- is CallMissedEvent -> {
1046
- val userId = event.user.id
1047
- android.util.Log.d("StreamCallPlugin", "Call was missed by user: $userId")
1048
- participantResponses[userId] = "missed"
1049
-
1050
- val data = JSObject().apply {
1051
- put("callId", callId)
1052
- put("state", "missed")
1053
- put("userId", userId)
1054
- }
1055
- notifyListeners("callEvent", data)
1056
-
1057
- // Check if all participants have rejected or missed
1058
- checkAllParticipantsResponded(participantResponses, totalParticipants, streamCall)
1059
- }
1060
- is CallAcceptedEvent -> {
1061
- val userId = event.user.id
1062
- android.util.Log.d("StreamCallPlugin", "Call was accepted by user: $userId")
1063
- participantResponses[userId] = "accepted"
1064
-
1065
- val data = JSObject().apply {
1066
- put("callId", callId)
1067
- put("state", "accepted")
1068
- put("userId", userId)
1069
- }
1070
- notifyListeners("callEvent", data)
1071
- }
1072
- }
1073
- }
1074
-
1095
+
1096
+ // Note: We no longer start tracking here - we'll wait for CallSessionStartedEvent
1097
+ // instead, which contains the actual participant list
1098
+
1075
1099
  android.util.Log.d("StreamCallPlugin", "Creating call with members...")
1076
1100
  // Create the call with all members
1077
1101
  streamCall?.create(
@@ -1107,41 +1131,229 @@ public class StreamCallPlugin : Plugin() {
1107
1131
  }
1108
1132
  }
1109
1133
 
1110
- @OptIn(DelicateCoroutinesApi::class)
1111
- private fun checkAllParticipantsResponded(
1112
- participantResponses: Map<String, String>,
1113
- totalParticipants: Int,
1114
- streamCall: Call
1115
- ) {
1116
- kotlinx.coroutines.GlobalScope.launch {
1117
- try {
1118
- val allResponded = participantResponses.size == totalParticipants
1119
- val allRejectedOrMissed = participantResponses.values.all { it == "rejected" || it == "missed" }
1134
+ private fun startCallTimeoutMonitor(callCid: String, memberIds: List<String>) {
1135
+ val callState = CallState(members = memberIds)
1136
+
1137
+ val handler = Handler(Looper.getMainLooper())
1138
+ val timeoutRunnable = object : Runnable {
1139
+ override fun run() {
1140
+ checkCallTimeout(callCid)
1141
+ handler.postDelayed(this, 1000)
1142
+ }
1143
+ }
1144
+
1145
+ handler.postDelayed(timeoutRunnable, 1000)
1146
+ callState.timer = handler
1147
+
1148
+ callStates[callCid] = callState
1149
+
1150
+ android.util.Log.d("StreamCallPlugin", "Started timeout monitor for call $callCid with ${memberIds.size} members")
1151
+ }
1152
+
1153
+ private fun checkCallTimeout(callCid: String) {
1154
+ val callState = callStates[callCid] ?: return
1155
+
1156
+ val now = System.currentTimeMillis()
1157
+ val elapsedSeconds = (now - callState.createdAt) / 1000
1158
+
1159
+ if (elapsedSeconds >= 30) {
1160
+ android.util.Log.d("StreamCallPlugin", "Call $callCid has timed out after $elapsedSeconds seconds")
1161
+
1162
+ val hasAccepted = callState.participantResponses.values.any { it == "accepted" }
1163
+
1164
+ if (!hasAccepted) {
1165
+ android.util.Log.d("StreamCallPlugin", "No one accepted call $callCid, marking all non-responders as missed")
1120
1166
 
1121
- if (allResponded && allRejectedOrMissed) {
1122
- android.util.Log.d("StreamCallPlugin", "All participants have rejected or missed the call")
1123
- streamCall.leave()
1124
- activity?.runOnUiThread {
1125
- overlayView?.setContent {
1126
- CallOverlayView(
1127
- context = context,
1128
- streamVideo = streamVideoClient,
1129
- call = null
1130
- )
1167
+ // First, remove the timer to prevent further callbacks
1168
+ callState.timer?.removeCallbacksAndMessages(null)
1169
+ callState.timer = null
1170
+
1171
+ callState.members.forEach { memberId ->
1172
+ if (memberId !in callState.participantResponses) {
1173
+ callState.participantResponses[memberId] = "missed"
1174
+
1175
+ val data = JSObject().apply {
1176
+ put("callId", callCid)
1177
+ put("state", "missed")
1178
+ put("userId", memberId)
1131
1179
  }
1132
- overlayView?.isVisible = false
1180
+ notifyListeners("callEvent", data)
1133
1181
  }
1182
+ }
1183
+
1184
+ val callIdParts = callCid.split(":")
1185
+ if (callIdParts.size >= 2) {
1186
+ val callType = callIdParts[0]
1187
+ val callId = callIdParts[1]
1134
1188
 
1135
- val data = JSObject().apply {
1136
- put("callId", streamCall.id)
1137
- put("state", "ended")
1138
- put("reason", "all_rejected_or_missed")
1189
+ streamVideoClient?.call(type = callType, id = callId)?.let { call ->
1190
+ kotlinx.coroutines.GlobalScope.launch {
1191
+ try {
1192
+ // Use endCallRaw instead of manual cleanup
1193
+ endCallRaw(call)
1194
+
1195
+ // Clean up state - we don't need to do this in endCallRaw because we already did it here
1196
+ callStates.remove(callCid)
1197
+
1198
+ // Notify that call has ended
1199
+ val data = JSObject().apply {
1200
+ put("callId", callCid)
1201
+ put("state", "ended")
1202
+ put("reason", "timeout")
1203
+ }
1204
+ notifyListeners("callEvent", data)
1205
+ } catch (e: Exception) {
1206
+ android.util.Log.e("StreamCallPlugin", "Error ending timed out call", e)
1207
+ }
1208
+ }
1209
+ }
1210
+ }
1211
+ }
1212
+ }
1213
+ }
1214
+
1215
+ private fun cleanupCall(callCid: String) {
1216
+ // Get the call state
1217
+ val callState = callStates[callCid]
1218
+
1219
+ if (callState != null) {
1220
+ // Ensure timer is properly canceled
1221
+ android.util.Log.d("StreamCallPlugin", "Stopping timer for call: $callCid")
1222
+ callState.timer?.removeCallbacksAndMessages(null)
1223
+ callState.timer = null
1224
+ }
1225
+
1226
+ // Remove from callStates
1227
+ callStates.remove(callCid)
1228
+
1229
+ // Hide UI elements directly without setting content
1230
+ runOnMainThread {
1231
+ android.util.Log.d("StreamCallPlugin", "Hiding UI elements for call $callCid (one-time cleanup)")
1232
+ overlayView?.isVisible = false
1233
+ ringtonePlayer?.stopRinging()
1234
+ incomingCallView?.isVisible = false
1235
+ }
1236
+
1237
+ android.util.Log.d("StreamCallPlugin", "Cleaned up resources for ended call: $callCid")
1238
+ }
1239
+
1240
+ private fun checkAllParticipantsResponded(callCid: String) {
1241
+ val callState = callStates[callCid] ?: return
1242
+
1243
+ val totalParticipants = callState.members.size
1244
+ val responseCount = callState.participantResponses.size
1245
+
1246
+ android.util.Log.d("StreamCallPlugin", "Checking responses for call $callCid: $responseCount / $totalParticipants")
1247
+
1248
+ val allResponded = responseCount >= totalParticipants
1249
+ val allRejectedOrMissed = allResponded &&
1250
+ callState.participantResponses.values.all { it == "rejected" || it == "missed" }
1251
+
1252
+ if (allResponded && allRejectedOrMissed) {
1253
+ android.util.Log.d("StreamCallPlugin", "All participants have rejected or missed the call $callCid")
1254
+
1255
+ // Cancel the timer immediately to prevent further callbacks
1256
+ callState.timer?.removeCallbacksAndMessages(null)
1257
+ callState.timer = null
1258
+
1259
+ // End the call using endCallRaw
1260
+ val callIdParts = callCid.split(":")
1261
+ if (callIdParts.size >= 2) {
1262
+ val callType = callIdParts[0]
1263
+ val callId = callIdParts[1]
1264
+
1265
+ streamVideoClient?.call(type = callType, id = callId)?.let { call ->
1266
+ kotlinx.coroutines.GlobalScope.launch {
1267
+ try {
1268
+ // Use endCallRaw instead of manual cleanup
1269
+ endCallRaw(call)
1270
+
1271
+ // Clean up state - we don't need to do this in endCallRaw because we already did it here
1272
+ callStates.remove(callCid)
1273
+
1274
+ // Notify that call has ended
1275
+ val data = JSObject().apply {
1276
+ put("callId", callCid)
1277
+ put("state", "ended")
1278
+ put("reason", "all_rejected_or_missed")
1279
+ }
1280
+ notifyListeners("callEvent", data)
1281
+ } catch (e: Exception) {
1282
+ android.util.Log.e("StreamCallPlugin", "Error ending call after all rejected/missed", e)
1283
+ }
1139
1284
  }
1140
- notifyListeners("callEvent", data)
1141
1285
  }
1142
- } catch (e: Exception) {
1143
- android.util.Log.e("StreamCallPlugin", "Error checking participant responses: ${e.message}")
1144
1286
  }
1145
1287
  }
1146
1288
  }
1289
+
1290
+ private suspend fun magicDeviceDelete(streamVideoClient: StreamVideo) {
1291
+ try {
1292
+ android.util.Log.d("StreamCallPlugin", "Starting magicDeviceDelete reflection operation")
1293
+
1294
+ // Get the streamNotificationManager field from StreamVideo
1295
+ val streamVideoClass = streamVideoClient.javaClass
1296
+ val notificationManagerField = streamVideoClass.getDeclaredField("streamNotificationManager")
1297
+ notificationManagerField.isAccessible = true
1298
+ val notificationManager = notificationManagerField.get(streamVideoClient)
1299
+
1300
+ if (notificationManager == null) {
1301
+ android.util.Log.e("StreamCallPlugin", "streamNotificationManager is null")
1302
+ return
1303
+ }
1304
+
1305
+ android.util.Log.d("StreamCallPlugin", "Successfully accessed streamNotificationManager")
1306
+
1307
+ // Get deviceTokenStorage from notification manager
1308
+ val notificationManagerClass = notificationManager.javaClass
1309
+ val deviceTokenStorageField = notificationManagerClass.getDeclaredField("deviceTokenStorage")
1310
+ deviceTokenStorageField.isAccessible = true
1311
+ val deviceTokenStorage = deviceTokenStorageField.get(notificationManager)
1312
+
1313
+ if (deviceTokenStorage == null) {
1314
+ android.util.Log.e("StreamCallPlugin", "deviceTokenStorage is null")
1315
+ return
1316
+ }
1317
+
1318
+ android.util.Log.d("StreamCallPlugin", "Successfully accessed deviceTokenStorage")
1319
+
1320
+ // Access the DeviceTokenStorage object dynamically without hardcoding class
1321
+ val deviceTokenStorageClass = deviceTokenStorage.javaClass
1322
+
1323
+ // Get the userDevice Flow from deviceTokenStorage
1324
+ val userDeviceField = deviceTokenStorageClass.getDeclaredField("userDevice")
1325
+ userDeviceField.isAccessible = true
1326
+ val userDeviceFlow = userDeviceField.get(deviceTokenStorage)
1327
+
1328
+ if (userDeviceFlow == null) {
1329
+ android.util.Log.e("StreamCallPlugin", "userDevice Flow is null")
1330
+ return
1331
+ }
1332
+
1333
+ android.util.Log.d("StreamCallPlugin", "Successfully accessed userDevice Flow: $userDeviceFlow")
1334
+
1335
+ val castedUserDeviceFlow = userDeviceFlow as Flow<Device?>
1336
+ try {
1337
+ castedUserDeviceFlow.first {
1338
+ if (it == null) {
1339
+ android.util.Log.d("StreamCallPlugin", "Device is null. Nothing to remove")
1340
+ return@first true;
1341
+ }
1342
+ streamVideoClient.deleteDevice(it)
1343
+ return@first true;
1344
+ }
1345
+ } catch (e: Throwable) {
1346
+ android.util.Log.e("StreamCallPlugin", "Cannot collect flow in magicDeviceDelete", e)
1347
+ }
1348
+ } catch (e: Exception) {
1349
+ android.util.Log.e("StreamCallPlugin", "Error in magicDeviceDelete", e)
1350
+ }
1351
+ }
1352
+
1353
+ data class CallState(
1354
+ val members: List<String>,
1355
+ val participantResponses: MutableMap<String, String> = mutableMapOf(),
1356
+ val createdAt: Long = System.currentTimeMillis(),
1357
+ var timer: Handler? = null
1358
+ )
1147
1359
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Uses the https://getstream.io/ SDK to implement calling in Capacitor",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",