@capgo/capacitor-stream-call 0.0.25-alpha.0 → 0.0.27

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.
@@ -1,5 +1,5 @@
1
1
  package ee.forgr.capacitor.streamcall
2
-
2
+
3
3
  import android.app.Application
4
4
  import android.app.Notification
5
5
  import android.app.NotificationManager
@@ -8,13 +8,17 @@ import android.content.Context
8
8
  import android.content.Intent
9
9
  import android.media.RingtoneManager
10
10
  import android.os.Build
11
+ import android.util.Log
11
12
  import androidx.core.app.NotificationCompat
13
+ import io.getstream.log.taggedLogger
14
+ import io.getstream.video.android.core.RingingState
12
15
  import io.getstream.video.android.core.notifications.DefaultNotificationHandler
16
+ import io.getstream.video.android.core.notifications.NotificationHandler
13
17
  import io.getstream.video.android.model.StreamCallId
14
-
18
+
15
19
  // declare "incoming_calls_custom" as a constant
16
20
  const val INCOMING_CALLS_CUSTOM = "incoming_calls_custom"
17
-
21
+
18
22
  class CustomNotificationHandler(
19
23
  val application: Application,
20
24
  private val endCall: (callId: StreamCallId) -> Unit = {},
@@ -25,17 +29,89 @@ class CustomNotificationHandler(
25
29
  private const val KEY_NOTIFICATION_TIME = "notification_creation_time"
26
30
  }
27
31
  private var allowSound = true;
28
-
29
- override fun getIncomingCallNotification(
32
+
33
+ override fun getRingingCallNotification(
34
+ ringingState: RingingState,
35
+ callId: StreamCallId,
36
+ callDisplayName: String?,
37
+ shouldHaveContentIntent: Boolean,
38
+ ): Notification? {
39
+ return if (ringingState is RingingState.Incoming) {
40
+ val fullScreenPendingIntent = intentResolver.searchIncomingCallPendingIntent(callId)
41
+ val acceptCallPendingIntent = intentResolver.searchAcceptCallPendingIntent(callId)
42
+ val rejectCallPendingIntent = intentResolver.searchRejectCallPendingIntent(callId)
43
+
44
+ if (fullScreenPendingIntent != null && acceptCallPendingIntent != null && rejectCallPendingIntent != null) {
45
+ customGetIncomingCallNotification(
46
+ fullScreenPendingIntent,
47
+ acceptCallPendingIntent,
48
+ rejectCallPendingIntent,
49
+ callDisplayName,
50
+ shouldHaveContentIntent,
51
+ callId
52
+ )
53
+ } else {
54
+ Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
55
+ null
56
+ }
57
+ } else if (ringingState is RingingState.Outgoing) {
58
+ val outgoingCallPendingIntent = intentResolver.searchOutgoingCallPendingIntent(callId)
59
+ val endCallPendingIntent = intentResolver.searchEndCallPendingIntent(callId)
60
+
61
+ if (outgoingCallPendingIntent != null && endCallPendingIntent != null) {
62
+ getOngoingCallNotification(
63
+ callId,
64
+ callDisplayName,
65
+ isOutgoingCall = true,
66
+ )
67
+ } else {
68
+ Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
69
+ null
70
+ }
71
+ } else {
72
+ null
73
+ }
74
+ }
75
+
76
+ fun customGetIncomingCallNotification(
30
77
  fullScreenPendingIntent: PendingIntent,
31
78
  acceptCallPendingIntent: PendingIntent,
32
79
  rejectCallPendingIntent: PendingIntent,
33
80
  callerName: String?,
34
81
  shouldHaveContentIntent: Boolean,
82
+ callId: StreamCallId
35
83
  ): Notification {
36
-
37
- customCreateIncomingCallChannel()
38
-
84
+
85
+ // customCreateIncomingCallChannel()
86
+ val manufacturer = Build.MANUFACTURER.lowercase()
87
+ if (manufacturer.contains("xiaomi") || manufacturer.contains("mi")) {
88
+ // val serviceIntent = Intent(application, CallForegroundService::class.java)
89
+ // serviceIntent.action = CallForegroundService.ACTION_START_FOREGROUND_SERVICE
90
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
91
+ // application.startForegroundService(serviceIntent)
92
+ // } else {
93
+ // application.startService(serviceIntent)
94
+ // }
95
+ // Adjust PendingIntent for Xiaomi to avoid permission denial
96
+ val xiaomiAcceptIntent = PendingIntent.getActivity(
97
+ application,
98
+ 0,
99
+ Intent("io.getstream.video.android.action.ACCEPT_CALL")
100
+ .setPackage(application.packageName)
101
+ .putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId),
102
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
103
+ )
104
+ return buildNotification(
105
+ fullScreenPendingIntent,
106
+ xiaomiAcceptIntent,
107
+ rejectCallPendingIntent,
108
+ callerName,
109
+ shouldHaveContentIntent,
110
+ INCOMING_CALLS_CUSTOM,
111
+ true // Include sound
112
+ )
113
+ }
114
+
39
115
  return buildNotification(
40
116
  fullScreenPendingIntent,
41
117
  acceptCallPendingIntent,
@@ -46,7 +122,7 @@ class CustomNotificationHandler(
46
122
  true // Include sound
47
123
  )
48
124
  }
49
-
125
+
50
126
  fun buildNotification(
51
127
  fullScreenPendingIntent: PendingIntent,
52
128
  acceptCallPendingIntent: PendingIntent,
@@ -64,10 +140,10 @@ class CustomNotificationHandler(
64
140
  setOngoing(true)
65
141
  setAutoCancel(false)
66
142
  setCategory(NotificationCompat.CATEGORY_CALL)
67
-
143
+
68
144
  // Clear all defaults first
69
145
  setDefaults(0)
70
-
146
+
71
147
  if (includeSound && allowSound) {
72
148
  setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))
73
149
  setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
@@ -75,7 +151,7 @@ class CustomNotificationHandler(
75
151
  setSound(null)
76
152
  setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
77
153
  }
78
-
154
+
79
155
  // setVibrate(longArrayOf(0, 1000, 500, 1000))
80
156
  setLights(0xFF0000FF.toInt(), 1000, 1000)
81
157
  setFullScreenIntent(fullScreenPendingIntent, true)
@@ -95,12 +171,12 @@ class CustomNotificationHandler(
95
171
  // flags = flags or NotificationCompat.FLAG_ONGOING_EVENT
96
172
  }
97
173
  }
98
-
174
+
99
175
  override fun onMissedCall(callId: StreamCallId, callDisplayName: String) {
100
176
  endCall(callId)
101
177
  super.onMissedCall(callId, callDisplayName)
102
178
  }
103
-
179
+
104
180
  private fun customCreateIncomingCallChannel() {
105
181
  maybeCreateChannel(
106
182
  channelId = INCOMING_CALLS_CUSTOM,
@@ -114,7 +190,7 @@ class CustomNotificationHandler(
114
190
  importance = NotificationManager.IMPORTANCE_HIGH
115
191
  this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
116
192
  this.setShowBadge(true)
117
-
193
+
118
194
  // Set the channel to be silent since we handle sound via RingtonePlayer
119
195
  setSound(null, null)
120
196
  enableVibration(true)
@@ -126,8 +202,9 @@ class CustomNotificationHandler(
126
202
  },
127
203
  )
128
204
  }
129
-
205
+
130
206
  public fun clone(): CustomNotificationHandler {
131
207
  return CustomNotificationHandler(this.application, this.endCall, this.incomingCall)
132
208
  }
133
209
  }
210
+
@@ -57,7 +57,7 @@ struct CallOverlayView: View {
57
57
  }
58
58
 
59
59
  private func changeTrackVisibility(_ participant: CallParticipant?, isVisible: Bool) {
60
- StreamCallPlugin.dualprint("changeTrackVisibility for \(String(describing: participant?.userId)), visible: \(isVisible)")
60
+ print("changeTrackVisibility for \(String(describing: participant?.userId)), visible: \(isVisible)")
61
61
  guard let participant = participant,
62
62
  let call = viewModel.call else { return }
63
63
  Task {
@@ -29,7 +29,7 @@ public struct CustomVideoCallParticipantView<Factory: ViewFactory>: View {
29
29
  customData: [String: RawJSON],
30
30
  call: Call?
31
31
  ) {
32
- StreamCallPlugin.dualprint("size: \(availableFrame.size)")
32
+ print("size: \(availableFrame.size)")
33
33
  self.viewFactory = viewFactory
34
34
  self.participant = participant
35
35
  self.id = id ?? participant.id
@@ -34,8 +34,8 @@ extension View {
34
34
  value: [ViewFramePreferenceData(label: label,
35
35
  frame: geo.frame(in: .global))])
36
36
  .onAppear {
37
- StreamCallPlugin.dualprint("ParticipantsView - Collecting frame for label: \(label)")
38
- StreamCallPlugin.dualprint("Frame: \(geo.frame(in: .global))")
37
+ print("ParticipantsView - Collecting frame for label: \(label)")
38
+ print("Frame: \(geo.frame(in: .global))")
39
39
  }
40
40
  }
41
41
  )
@@ -5,7 +5,6 @@ import StreamVideoSwiftUI
5
5
  import SwiftUI
6
6
  import Combine
7
7
  import WebKit
8
- import os.log
9
8
 
10
9
  /**
11
10
  * Please read the Capacitor iOS Plugin Development Guide
@@ -28,15 +27,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
28
27
  CAPPluginMethod(name: "getCallStatus", returnType: CAPPluginReturnPromise)
29
28
  ]
30
29
 
31
- // OSLog for streamcall plugin
32
- private static let osLog = OSLog(subsystem: "com.example.plugin.streamcall", category: "StreamCallPlugin")
33
-
34
- // Static function for dual printing to console and os.log
35
- public static func dualprint(_ message: String) {
36
- print(message)
37
- os_log("%{public}@", log: osLog, type: .info, message)
38
- }
39
-
40
30
  private enum State {
41
31
  case notInitialized
42
32
  case initializing
@@ -44,8 +34,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
44
34
  }
45
35
  private var apiKey: String?
46
36
  private var state: State = .notInitialized
47
- private static let tokenRefreshQueue = DispatchQueue(label: "stream.call.token.refresh")
48
- private static let tokenRefreshSemaphore = DispatchSemaphore(value: 1)
49
37
  private var currentToken: String?
50
38
  private var tokenWaitSemaphore: DispatchSemaphore?
51
39
 
@@ -66,9 +54,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
66
54
  @Injected(\.callKitPushNotificationAdapter) var callKitPushNotificationAdapter
67
55
  private var webviewDelegate: WebviewNavigationDelegate?
68
56
 
69
- // Add class property to store call states
70
- private var callStates: [String: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)] = [:]
71
-
72
57
  // Declare as optional and initialize in load() method
73
58
  private var callViewModel: CallViewModel?
74
59
 
@@ -107,7 +92,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
107
92
 
108
93
  // Check if we have a logged in user for handling incoming calls
109
94
  if let credentials = SecureUserRepository.shared.loadCurrentUser() {
110
- StreamCallPlugin.dualprint("Loading user for StreamCallPlugin: \(credentials.user.name)")
95
+ print("Loading user for StreamCallPlugin: \(credentials.user.name)")
111
96
  DispatchQueue.global(qos: .userInitiated).async {
112
97
  self.initializeStreamVideo()
113
98
  }
@@ -118,7 +103,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
118
103
  wrappedDelegate: self.webView?.navigationDelegate,
119
104
  onSetupOverlay: { [weak self] in
120
105
  guard let self = self else { return }
121
- StreamCallPlugin.dualprint("Attempting to setup call view")
106
+ print("Attempting to setup call view")
122
107
 
123
108
  self.setupViews()
124
109
  }
@@ -138,7 +123,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
138
123
  return
139
124
  }
140
125
 
141
- StreamCallPlugin.dualprint("loginMagicToken received token")
126
+ print("loginMagicToken received token")
142
127
  currentToken = token
143
128
  tokenWaitSemaphore?.signal()
144
129
  call.resolve()
@@ -153,17 +138,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
153
138
  guard let self = self else { return }
154
139
  Task {
155
140
  do {
156
- StreamCallPlugin.dualprint("Setting up token subscription")
141
+ print("Setting up token subscription")
157
142
  try self.requireInitialized()
158
143
  if let lastVoIPToken = self.lastVoIPToken, !lastVoIPToken.isEmpty {
159
- StreamCallPlugin.dualprint("Deleting device: \(lastVoIPToken)")
144
+ print("Deleting device: \(lastVoIPToken)")
160
145
  try await self.streamVideo?.deleteDevice(id: lastVoIPToken)
161
146
  }
162
147
  if !updatedDeviceToken.isEmpty {
163
- StreamCallPlugin.dualprint("Setting voip device: \(updatedDeviceToken)")
148
+ print("Setting voip device: \(updatedDeviceToken)")
164
149
  try await self.streamVideo?.setVoipDevice(id: updatedDeviceToken)
165
150
  // Save the token to our secure storage
166
- StreamCallPlugin.dualprint("Saving voip token: \(updatedDeviceToken)")
151
+ print("Saving voip token: \(updatedDeviceToken)")
167
152
  SecureUserRepository.shared.save(voipPushToken: updatedDeviceToken)
168
153
  }
169
154
  self.lastVoIPToken = updatedDeviceToken
@@ -185,7 +170,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
185
170
 
186
171
  // Verify callViewModel exists
187
172
  guard let callViewModel = self.callViewModel, let streamVideo = self.streamVideo else {
188
- StreamCallPlugin.dualprint("Warning: setupActiveCallSubscription called but callViewModel or streamVideo is nil")
173
+ print("Warning: setupActiveCallSubscription called but callViewModel or streamVideo is nil")
189
174
  // Schedule a retry after a short delay if callViewModel is nil
190
175
  DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
191
176
  self?.setupActiveCallSubscription()
@@ -193,7 +178,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
193
178
  return
194
179
  }
195
180
 
196
- StreamCallPlugin.dualprint("Setting up active call subscription")
181
+ print("Setting up active call subscription")
197
182
 
198
183
  // Create a strong reference to callViewModel to ensure it's not deallocated
199
184
  // while the subscription is active
@@ -205,7 +190,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
205
190
  .sink { [weak self, weak viewModel] activeCall in
206
191
  guard let self = self, let viewModel = viewModel else { return }
207
192
 
208
- StreamCallPlugin.dualprint("Active call update from streamVideo: \(String(describing: activeCall?.cId))")
193
+ print("Active call update from streamVideo: \(String(describing: activeCall?.cId))")
209
194
 
210
195
  if let activeCall = activeCall {
211
196
  // Sync callViewModel with activeCall from streamVideo state
@@ -222,24 +207,24 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
222
207
  .receive(on: DispatchQueue.main)
223
208
  .sink { [weak self, weak viewModel] newState in
224
209
  guard let self = self, let viewModel = viewModel else {
225
- StreamCallPlugin.dualprint("Warning: Call state update received but self or viewModel is nil")
210
+ print("Warning: Call state update received but self or viewModel is nil")
226
211
  return
227
212
  }
228
213
 
229
214
  do {
230
215
  try self.requireInitialized()
231
- StreamCallPlugin.dualprint("Call State Update: \(newState)")
216
+ print("Call State Update: \(newState)")
232
217
 
233
218
  if newState == .inCall {
234
- StreamCallPlugin.dualprint("- In call state detected")
235
- StreamCallPlugin.dualprint("- All participants: \(String(describing: viewModel.participants))")
219
+ print("- In call state detected")
220
+ print("- All participants: \(String(describing: viewModel.participants))")
236
221
 
237
222
  // Create/update overlay and make visible when there's an active call
238
223
  self.createCallOverlayView()
239
224
 
240
225
  // Notify that a call has started - but only if we haven't notified for this call yet
241
226
  if let callId = viewModel.call?.cId, !self.hasNotifiedCallJoined || callId != self.currentCallId {
242
- StreamCallPlugin.dualprint("Notifying call joined: \(callId)")
227
+ print("Notifying call joined: \(callId)")
243
228
  self.updateCallStatusAndNotify(callId: callId, state: "joined")
244
229
  self.hasNotifiedCallJoined = true
245
230
  }
@@ -248,7 +233,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
248
233
  } else if newState == .idle && self.streamVideo?.state.activeCall == nil {
249
234
  // Get the call ID that was active before the state changed
250
235
  let endingCallId = viewModel.call?.cId
251
- StreamCallPlugin.dualprint("Call ending: \(String(describing: endingCallId))")
236
+ print("Call ending: \(String(describing: endingCallId))")
252
237
 
253
238
  // Notify that call has ended - use the properly tracked call ID
254
239
  self.updateCallStatusAndNotify(callId: endingCallId ?? "", state: "left")
@@ -256,17 +241,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
256
241
  // Reset notification flag when call ends
257
242
  self.hasNotifiedCallJoined = false
258
243
 
259
- // Clean up any resources for this call
260
- if let callCid = endingCallId {
261
- // Invalidate and remove the timer
262
- self.callStates[callCid]?.timer?.invalidate()
263
-
264
- // Remove call from callStates
265
- self.callStates.removeValue(forKey: callCid)
266
-
267
- StreamCallPlugin.dualprint("Cleaned up resources for ended call: \(callCid)")
268
- }
269
-
270
244
  // Remove the call overlay view when not in a call
271
245
  self.ensureViewRemoved()
272
246
  }
@@ -281,7 +255,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
281
255
  statePublisher.cancel()
282
256
  }
283
257
 
284
- StreamCallPlugin.dualprint("Active call subscription setup completed")
258
+ print("Active call subscription setup completed")
285
259
 
286
260
  // Schedule a periodic check to ensure subscription is active
287
261
  self.scheduleSubscriptionCheck()
@@ -296,7 +270,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
296
270
 
297
271
  // Check if we're in a state where we need the subscription but it's not active
298
272
  if self.state == .initialized && self.activeCallSubscription == nil && self.callViewModel != nil {
299
- StreamCallPlugin.dualprint("Subscription check: Restoring lost activeCallSubscription")
273
+ print("Subscription check: Restoring lost activeCallSubscription")
300
274
  self.setupActiveCallSubscription()
301
275
  } else {
302
276
  // Schedule the next check
@@ -305,134 +279,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
305
279
  }
306
280
  }
307
281
 
308
- @objc private func checkCallTimeoutTimer(_ timer: Timer) {
309
- guard let callCid = timer.userInfo as? String else { return }
310
-
311
- Task { [weak self] in
312
- guard let self = self else { return }
313
- await self.checkCallTimeout(callCid: callCid)
314
- }
315
- }
316
-
317
- private func checkCallTimeout(callCid: String) async {
318
- // Get a local copy of the call state from the main thread
319
- let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
320
- return self.callStates[callCid]
321
- }
322
-
323
- guard let callState = callState else { return }
324
-
325
- // Calculate time elapsed since call creation
326
- let now = Date()
327
- let elapsedSeconds = now.timeIntervalSince(callState.createdAt)
328
-
329
- // Check if 30 seconds have passed
330
- if elapsedSeconds >= 30.0 {
331
-
332
- // Check if anyone has accepted
333
- let hasAccepted = callState.participantResponses.values.contains { $0 == "accepted" }
334
-
335
- if !hasAccepted {
336
- StreamCallPlugin.dualprint("Call \(callCid) has timed out after \(elapsedSeconds) seconds")
337
- StreamCallPlugin.dualprint("No one accepted call \(callCid), marking all non-responders as missed")
338
-
339
- // Mark all members who haven't responded as "missed"
340
- for member in callState.members {
341
- let memberId = member.userId
342
- let needsToBeMarkedAsMissed = await MainActor.run {
343
- return self.callStates[callCid]?.participantResponses[memberId] == nil
344
- }
345
-
346
- if needsToBeMarkedAsMissed {
347
- // Update callStates map on main thread
348
- await MainActor.run {
349
- var updatedCallState = self.callStates[callCid]
350
- updatedCallState?.participantResponses[memberId] = "missed"
351
- if let updatedCallState = updatedCallState {
352
- self.callStates[callCid] = updatedCallState
353
- }
354
- }
355
-
356
- // Notify listeners
357
- await MainActor.run {
358
- self.updateCallStatusAndNotify(callId: callCid, state: "missed", userId: memberId)
359
- }
360
- }
361
- }
362
-
363
- // End the call
364
- if let call = streamVideo?.state.activeCall, call.cId == callCid {
365
- call.leave()
366
- }
367
-
368
- // Clean up timer on main thread
369
- await MainActor.run {
370
- self.callStates[callCid]?.timer?.invalidate()
371
- var updatedCallState = self.callStates[callCid]
372
- updatedCallState?.timer = nil
373
- if let updatedCallState = updatedCallState {
374
- self.callStates[callCid] = updatedCallState
375
- }
376
-
377
- // Remove from callStates
378
- self.callStates.removeValue(forKey: callCid)
379
- }
380
-
381
- // Update UI
382
- await MainActor.run {
383
- // self.overlayViewModel?.updateCall(nil)
384
- self.overlayView?.isHidden = true
385
- self.webView?.isOpaque = true
386
- self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "timeout")
387
- }
388
- }
389
- }
390
- }
391
-
392
- private func checkAllParticipantsResponded(callCid: String) async {
393
- // Get a local copy of the call state from the main thread
394
- let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
395
- return self.callStates[callCid]
396
- }
397
-
398
- guard let callState = callState else {
399
- StreamCallPlugin.dualprint("Call state not found for cId: \(callCid)")
400
- return
401
- }
402
-
403
- let totalParticipants = callState.members.count
404
- let responseCount = callState.participantResponses.count
405
-
406
- StreamCallPlugin.dualprint("Total participants: \(totalParticipants), Responses: \(responseCount)")
407
-
408
- let allResponded = responseCount >= totalParticipants
409
- let allRejectedOrMissed = allResponded &&
410
- callState.participantResponses.values.allSatisfy { $0 == "rejected" || $0 == "missed" }
411
-
412
- if allResponded && allRejectedOrMissed {
413
- StreamCallPlugin.dualprint("All participants have rejected or missed the call")
414
-
415
- // End the call
416
- if let call = streamVideo?.state.activeCall, call.cId == callCid {
417
- call.leave()
418
- }
419
-
420
- // Clean up timer and remove from callStates on main thread
421
- await MainActor.run {
422
- // Clean up timer
423
- self.callStates[callCid]?.timer?.invalidate()
424
-
425
- // Remove from callStates
426
- self.callStates.removeValue(forKey: callCid)
427
-
428
- // self.overlayViewModel?.updateCall(nil)
429
- self.overlayView?.isHidden = true
430
- self.webView?.isOpaque = true
431
- self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "all_rejected_or_missed")
432
- }
433
- }
434
- }
435
-
436
282
  @objc func login(_ call: CAPPluginCall) {
437
283
  guard let token = call.getString("token"),
438
284
  let userId = call.getString("userId"),
@@ -554,30 +400,26 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
554
400
 
555
401
  Task {
556
402
  do {
557
- StreamCallPlugin.dualprint("Creating call:")
558
- StreamCallPlugin.dualprint("- Call ID: \(callId)")
559
- StreamCallPlugin.dualprint("- Call Type: \(callType)")
560
- StreamCallPlugin.dualprint("- Users: \(members)")
561
- StreamCallPlugin.dualprint("- Should Ring: \(shouldRing)")
562
- StreamCallPlugin.dualprint("- Team: \(team)")
403
+ print("Creating call:")
404
+ print("- Call ID: \(callId)")
405
+ print("- Call Type: \(callType)")
406
+ print("- Users: \(members)")
407
+ print("- Should Ring: \(shouldRing)")
408
+ print("- Team: \(team)")
563
409
 
564
- // Create the call object
565
- let streamCall = streamVideo?.call(callType: callType, callId: callId)
566
-
567
- // Start the call with the members
568
- StreamCallPlugin.dualprint("Creating call with members...")
569
- try await streamCall?.create(
570
- memberIds: members,
571
- custom: [:],
572
- team: team, ring: shouldRing
573
- )
574
410
 
575
- // Join the call
576
- StreamCallPlugin.dualprint("Joining call...")
577
- try await streamCall?.join(create: false)
578
- StreamCallPlugin.dualprint("Successfully joined call")
579
411
 
580
412
  // Update the CallOverlayView with the active call
413
+ // Create the call object
414
+ await self.callViewModel?.startCall(
415
+ callType: callType,
416
+ callId: callId,
417
+ members: members.map { Member(userId: $0, role: nil, customData: [:], updatedAt: nil) },
418
+ ring: shouldRing
419
+ )
420
+
421
+
422
+ // Update UI on main thread
581
423
  await MainActor.run {
582
424
  // self.overlayViewModel?.updateCall(streamCall)
583
425
  self.overlayView?.isHidden = false
@@ -586,7 +428,8 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
586
428
 
587
429
  call.resolve([
588
430
  "success": true
589
- ])
431
+ ])
432
+
590
433
  } catch {
591
434
  log.error("Error making call: \(String(describing: error))")
592
435
  call.reject("Failed to make call: \(error.localizedDescription)")
@@ -705,14 +548,14 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
705
548
  }
706
549
 
707
550
  // Join the call
708
- StreamCallPlugin.dualprint("Accepting and joining call \(streamCall!.cId)...")
551
+ print("Accepting and joining call \(streamCall!.cId)...")
709
552
  guard case .incoming(let incomingCall) = await self.callViewModel?.callingState else {
710
553
  call.reject("Failed to accept call as there is no call ID")
711
554
  return
712
555
  }
713
556
 
714
557
  await self.callViewModel?.acceptCall(callType: incomingCall.type, callId: incomingCall.id)
715
- StreamCallPlugin.dualprint("Successfully joined call")
558
+ print("Successfully joined call")
716
559
 
717
560
  // Update the CallOverlayView with the active call
718
561
  await MainActor.run {
@@ -736,18 +579,18 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
736
579
 
737
580
  private func initializeStreamVideo() {
738
581
  if (state == .initialized) {
739
- StreamCallPlugin.dualprint("initializeStreamVideo already initialized")
582
+ print("initializeStreamVideo already initialized")
740
583
  // Try to get user credentials from repository
741
584
  guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
742
- StreamCallPlugin.dualprint("Save credentials not found, skipping initialization")
585
+ print("Save credentials not found, skipping initialization")
743
586
  return
744
587
  }
745
588
  if (savedCredentials.user.id == streamVideo?.user.id) {
746
- StreamCallPlugin.dualprint("Skipping initializeStreamVideo as user is already logged in")
589
+ print("Skipping initializeStreamVideo as user is already logged in")
747
590
  return
748
591
  }
749
592
  } else if (state == .initializing) {
750
- StreamCallPlugin.dualprint("initializeStreamVideo rejected - already initializing")
593
+ print("initializeStreamVideo rejected - already initializing")
751
594
  return
752
595
  }
753
596
  state = .initializing
@@ -755,11 +598,11 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
755
598
  // Try to get user credentials from repository
756
599
  guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser(),
757
600
  let apiKey = self.apiKey else {
758
- StreamCallPlugin.dualprint("No saved credentials or API key found, skipping initialization")
601
+ print("No saved credentials or API key found, skipping initialization")
759
602
  state = .notInitialized
760
603
  return
761
604
  }
762
- StreamCallPlugin.dualprint("Initializing with saved credentials for user: \(savedCredentials.user.name)")
605
+ print("Initializing with saved credentials for user: \(savedCredentials.user.name)")
763
606
 
764
607
  LogConfig.level = .debug
765
608
  self.streamVideo = StreamVideo(
@@ -768,7 +611,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
768
611
  token: UserToken(stringLiteral: savedCredentials.tokenValue),
769
612
  tokenProvider: {completion in
770
613
  guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
771
- StreamCallPlugin.dualprint("No saved credentials or API key found, cannot refresh token")
614
+ print("No saved credentials or API key found, cannot refresh token")
772
615
 
773
616
  completion(.failure(NSError(domain: "No saved credentials or API key found, cannot refresh token", code: 0, userInfo: nil)))
774
617
  return
@@ -861,7 +704,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
861
704
 
862
705
  // Check if we already have an overlay view - do nothing if it exists
863
706
  if let existingOverlayView = self.overlayView, existingOverlayView.superview != nil {
864
- StreamCallPlugin.dualprint("Call overlay view already exists, making it visible")
707
+ print("Call overlay view already exists, making it visible")
865
708
  existingOverlayView.isHidden = false
866
709
  // Make webview transparent to see StreamCall UI underneath
867
710
  webView.isOpaque = false
@@ -870,7 +713,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
870
713
  return
871
714
  }
872
715
 
873
- StreamCallPlugin.dualprint("Creating new call overlay view")
716
+ print("Creating new call overlay view")
874
717
 
875
718
  // First, create the overlay view
876
719
  let overlayView = UIHostingController(rootView: CallOverlayView(viewModel: callOverlayView))
@@ -923,7 +766,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
923
766
  private func ensureViewRemoved() {
924
767
  // Check if we have an overlay view
925
768
  if let existingOverlayView = self.overlayView {
926
- StreamCallPlugin.dualprint("Hiding call overlay view")
769
+ print("Hiding call overlay view")
927
770
 
928
771
  // Hide the view instead of removing it
929
772
  existingOverlayView.isHidden = true
@@ -933,7 +776,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
933
776
  self.webView?.backgroundColor = nil
934
777
  self.webView?.scrollView.backgroundColor = nil
935
778
  } else {
936
- StreamCallPlugin.dualprint("No call overlay view to hide")
779
+ print("No call overlay view to hide")
937
780
  }
938
781
  }
939
782
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.25-alpha.0",
3
+ "version": "0.0.27",
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",