@capgo/capacitor-stream-call 0.0.26 → 0.0.28

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.
@@ -24,7 +24,9 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
24
24
  CAPPluginMethod(name: "setCameraEnabled", returnType: CAPPluginReturnPromise),
25
25
  CAPPluginMethod(name: "acceptCall", returnType: CAPPluginReturnPromise),
26
26
  CAPPluginMethod(name: "isCameraEnabled", returnType: CAPPluginReturnPromise),
27
- CAPPluginMethod(name: "getCallStatus", returnType: CAPPluginReturnPromise)
27
+ CAPPluginMethod(name: "getCallStatus", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "setSpeaker", returnType: CAPPluginReturnPromise),
29
+ CAPPluginMethod(name: "switchCamera", returnType: CAPPluginReturnPromise)
28
30
  ]
29
31
 
30
32
  private enum State {
@@ -34,8 +36,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
34
36
  }
35
37
  private var apiKey: String?
36
38
  private var state: State = .notInitialized
37
- private static let tokenRefreshQueue = DispatchQueue(label: "stream.call.token.refresh")
38
- private static let tokenRefreshSemaphore = DispatchSemaphore(value: 1)
39
39
  private var currentToken: String?
40
40
  private var tokenWaitSemaphore: DispatchSemaphore?
41
41
 
@@ -56,9 +56,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
56
56
  @Injected(\.callKitPushNotificationAdapter) var callKitPushNotificationAdapter
57
57
  private var webviewDelegate: WebviewNavigationDelegate?
58
58
 
59
- // Add class property to store call states
60
- private var callStates: [String: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)] = [:]
61
-
62
59
  // Declare as optional and initialize in load() method
63
60
  private var callViewModel: CallViewModel?
64
61
 
@@ -246,17 +243,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
246
243
  // Reset notification flag when call ends
247
244
  self.hasNotifiedCallJoined = false
248
245
 
249
- // Clean up any resources for this call
250
- if let callCid = endingCallId {
251
- // Invalidate and remove the timer
252
- self.callStates[callCid]?.timer?.invalidate()
253
-
254
- // Remove call from callStates
255
- self.callStates.removeValue(forKey: callCid)
256
-
257
- print("Cleaned up resources for ended call: \(callCid)")
258
- }
259
-
260
246
  // Remove the call overlay view when not in a call
261
247
  self.ensureViewRemoved()
262
248
  }
@@ -295,134 +281,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
295
281
  }
296
282
  }
297
283
 
298
- @objc private func checkCallTimeoutTimer(_ timer: Timer) {
299
- guard let callCid = timer.userInfo as? String else { return }
300
-
301
- Task { [weak self] in
302
- guard let self = self else { return }
303
- await self.checkCallTimeout(callCid: callCid)
304
- }
305
- }
306
-
307
- private func checkCallTimeout(callCid: String) async {
308
- // Get a local copy of the call state from the main thread
309
- let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
310
- return self.callStates[callCid]
311
- }
312
-
313
- guard let callState = callState else { return }
314
-
315
- // Calculate time elapsed since call creation
316
- let now = Date()
317
- let elapsedSeconds = now.timeIntervalSince(callState.createdAt)
318
-
319
- // Check if 30 seconds have passed
320
- if elapsedSeconds >= 30.0 {
321
-
322
- // Check if anyone has accepted
323
- let hasAccepted = callState.participantResponses.values.contains { $0 == "accepted" }
324
-
325
- if !hasAccepted {
326
- print("Call \(callCid) has timed out after \(elapsedSeconds) seconds")
327
- print("No one accepted call \(callCid), marking all non-responders as missed")
328
-
329
- // Mark all members who haven't responded as "missed"
330
- for member in callState.members {
331
- let memberId = member.userId
332
- let needsToBeMarkedAsMissed = await MainActor.run {
333
- return self.callStates[callCid]?.participantResponses[memberId] == nil
334
- }
335
-
336
- if needsToBeMarkedAsMissed {
337
- // Update callStates map on main thread
338
- await MainActor.run {
339
- var updatedCallState = self.callStates[callCid]
340
- updatedCallState?.participantResponses[memberId] = "missed"
341
- if let updatedCallState = updatedCallState {
342
- self.callStates[callCid] = updatedCallState
343
- }
344
- }
345
-
346
- // Notify listeners
347
- await MainActor.run {
348
- self.updateCallStatusAndNotify(callId: callCid, state: "missed", userId: memberId)
349
- }
350
- }
351
- }
352
-
353
- // End the call
354
- if let call = streamVideo?.state.activeCall, call.cId == callCid {
355
- call.leave()
356
- }
357
-
358
- // Clean up timer on main thread
359
- await MainActor.run {
360
- self.callStates[callCid]?.timer?.invalidate()
361
- var updatedCallState = self.callStates[callCid]
362
- updatedCallState?.timer = nil
363
- if let updatedCallState = updatedCallState {
364
- self.callStates[callCid] = updatedCallState
365
- }
366
-
367
- // Remove from callStates
368
- self.callStates.removeValue(forKey: callCid)
369
- }
370
-
371
- // Update UI
372
- await MainActor.run {
373
- // self.overlayViewModel?.updateCall(nil)
374
- self.overlayView?.isHidden = true
375
- self.webView?.isOpaque = true
376
- self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "timeout")
377
- }
378
- }
379
- }
380
- }
381
-
382
- private func checkAllParticipantsResponded(callCid: String) async {
383
- // Get a local copy of the call state from the main thread
384
- let callState: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)? = await MainActor.run {
385
- return self.callStates[callCid]
386
- }
387
-
388
- guard let callState = callState else {
389
- print("Call state not found for cId: \(callCid)")
390
- return
391
- }
392
-
393
- let totalParticipants = callState.members.count
394
- let responseCount = callState.participantResponses.count
395
-
396
- print("Total participants: \(totalParticipants), Responses: \(responseCount)")
397
-
398
- let allResponded = responseCount >= totalParticipants
399
- let allRejectedOrMissed = allResponded &&
400
- callState.participantResponses.values.allSatisfy { $0 == "rejected" || $0 == "missed" }
401
-
402
- if allResponded && allRejectedOrMissed {
403
- print("All participants have rejected or missed the call")
404
-
405
- // End the call
406
- if let call = streamVideo?.state.activeCall, call.cId == callCid {
407
- call.leave()
408
- }
409
-
410
- // Clean up timer and remove from callStates on main thread
411
- await MainActor.run {
412
- // Clean up timer
413
- self.callStates[callCid]?.timer?.invalidate()
414
-
415
- // Remove from callStates
416
- self.callStates.removeValue(forKey: callCid)
417
-
418
- // self.overlayViewModel?.updateCall(nil)
419
- self.overlayView?.isHidden = true
420
- self.webView?.isOpaque = true
421
- self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "all_rejected_or_missed")
422
- }
423
- }
424
- }
425
-
426
284
  @objc func login(_ call: CAPPluginCall) {
427
285
  guard let token = call.getString("token"),
428
286
  let userId = call.getString("userId"),
@@ -551,23 +409,19 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
551
409
  print("- Should Ring: \(shouldRing)")
552
410
  print("- Team: \(team)")
553
411
 
554
- // Create the call object
555
- let streamCall = streamVideo?.call(callType: callType, callId: callId)
556
-
557
- // Start the call with the members
558
- print("Creating call with members...")
559
- try await streamCall?.create(
560
- memberIds: members,
561
- custom: [:],
562
- team: team, ring: shouldRing
563
- )
564
412
 
565
- // Join the call
566
- print("Joining call...")
567
- try await streamCall?.join(create: false)
568
- print("Successfully joined call")
569
413
 
570
414
  // Update the CallOverlayView with the active call
415
+ // Create the call object
416
+ await self.callViewModel?.startCall(
417
+ callType: callType,
418
+ callId: callId,
419
+ members: members.map { Member(userId: $0, role: nil, customData: [:], updatedAt: nil) },
420
+ ring: shouldRing
421
+ )
422
+
423
+
424
+ // Update UI on main thread
571
425
  await MainActor.run {
572
426
  // self.overlayViewModel?.updateCall(streamCall)
573
427
  self.overlayView?.isHidden = false
@@ -576,7 +430,8 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
576
430
 
577
431
  call.resolve([
578
432
  "success": true
579
- ])
433
+ ])
434
+
580
435
  } catch {
581
436
  log.error("Error making call: \(String(describing: error))")
582
437
  call.reject("Failed to make call: \(error.localizedDescription)")
@@ -939,4 +794,66 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
939
794
  "state": currentCallState
940
795
  ])
941
796
  }
797
+
798
+ @objc func setSpeaker(_ call: CAPPluginCall) {
799
+ guard let name = call.getString("name") else {
800
+ call.reject("Missing required parameter: name")
801
+ return
802
+ }
803
+
804
+ do {
805
+ try requireInitialized()
806
+
807
+ Task {
808
+ do {
809
+ if let activeCall = streamVideo?.state.activeCall {
810
+ if name == "speaker" {
811
+ try await activeCall.speaker.enableSpeakerPhone()
812
+ } else {
813
+ try await activeCall.speaker.disableSpeakerPhone()
814
+ }
815
+ }
816
+ call.resolve([
817
+ "success": true
818
+ ])
819
+ } catch {
820
+ log.error("Error setting speaker: \(String(describing: error))")
821
+ call.reject("Failed to set speaker: \(error.localizedDescription)")
822
+ }
823
+ }
824
+ } catch {
825
+ call.reject("StreamVideo not initialized")
826
+ }
827
+ }
828
+
829
+ @objc func switchCamera(_ call: CAPPluginCall) {
830
+ guard let camera = call.getString("camera") else {
831
+ call.reject("Missing required parameter: camera")
832
+ return
833
+ }
834
+
835
+ do {
836
+ try requireInitialized()
837
+
838
+ Task {
839
+ do {
840
+ if let activeCall = streamVideo?.state.activeCall {
841
+ if (camera == "front" && activeCall.camera.direction != .front) ||
842
+ (camera == "back" && activeCall.camera.direction != .back) {
843
+ try await activeCall.camera.flip()
844
+ }
845
+ }
846
+ call.resolve([
847
+ "success": true
848
+ ])
849
+ } catch {
850
+ log.error("Error switching camera: \(String(describing: error))")
851
+ call.reject("Failed to switch camera: \(error.localizedDescription)")
852
+ }
853
+ }
854
+ } catch {
855
+ call.reject("StreamVideo not initialized")
856
+ }
857
+ }
858
+
942
859
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
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",
@@ -1,217 +0,0 @@
1
- package ee.forgr.capacitor.streamcall
2
-
3
- import android.content.Context
4
- import android.widget.Toast
5
- import androidx.compose.foundation.background
6
- import androidx.compose.foundation.layout.Box
7
- import androidx.compose.foundation.layout.BoxScope
8
- import androidx.compose.foundation.layout.WindowInsets
9
- import androidx.compose.foundation.layout.asPaddingValues
10
- import androidx.compose.foundation.layout.fillMaxSize
11
- import androidx.compose.foundation.layout.padding
12
- import androidx.compose.foundation.layout.safeDrawing
13
- import androidx.compose.material3.Text
14
- import androidx.compose.runtime.Composable
15
- import androidx.compose.runtime.LaunchedEffect
16
- import androidx.compose.runtime.collectAsState
17
- import androidx.compose.runtime.getValue
18
- import androidx.compose.runtime.mutableStateOf
19
- import androidx.compose.runtime.remember
20
- import androidx.compose.runtime.setValue
21
- import androidx.compose.ui.Alignment
22
- import androidx.compose.ui.Modifier
23
- import androidx.compose.ui.draw.clip
24
- import androidx.compose.ui.layout.onSizeChanged
25
- import androidx.compose.ui.platform.LocalConfiguration
26
- import androidx.compose.ui.platform.LocalInspectionMode
27
- import androidx.compose.ui.platform.LocalLayoutDirection
28
- import androidx.compose.ui.platform.LocalDensity
29
- import androidx.compose.ui.text.style.TextAlign
30
- import androidx.compose.ui.unit.IntSize
31
- import androidx.compose.ui.unit.dp
32
- import androidx.compose.ui.unit.sp
33
- import io.getstream.video.android.compose.permission.LaunchCallPermissions
34
- import io.getstream.video.android.compose.theme.VideoTheme
35
- import io.getstream.video.android.compose.ui.components.call.renderer.FloatingParticipantVideo
36
- import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantVideo
37
- import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantsLayout
38
- import io.getstream.video.android.compose.ui.components.call.renderer.RegularVideoRendererStyle
39
- import io.getstream.video.android.compose.ui.components.call.renderer.VideoRendererStyle
40
- import io.getstream.video.android.core.ParticipantState
41
- import io.getstream.video.android.core.RealtimeConnection
42
- import io.getstream.video.android.core.StreamVideo
43
- import io.getstream.video.android.core.Call
44
- import io.getstream.video.android.compose.ui.components.video.VideoScalingType
45
- import androidx.compose.ui.graphics.Color
46
-
47
- @Composable
48
- fun CallOverlayView(
49
- context: Context,
50
- streamVideo: StreamVideo?,
51
- call: Call?
52
- ) {
53
- if (streamVideo == null) {
54
- Box(
55
- modifier = Modifier
56
- .fillMaxSize()
57
- .background(Color.Red)
58
- )
59
- return
60
- }
61
-
62
- // Collect the active call state
63
- //val activeCall by internalInstance.state.activeCall.collectAsState()
64
- // val call = activeCall
65
- if (call == null) {
66
- Box(
67
- modifier = Modifier
68
- .fillMaxSize()
69
- .background(Color.LightGray)
70
- )
71
- return
72
- }
73
-
74
- // Handle permissions in the Composable context
75
- LaunchCallPermissions(
76
- call = call,
77
- onAllPermissionsGranted = {
78
- try {
79
- // Check session using reflection before joining
80
- val callClass = call.javaClass
81
- val sessionField = callClass.getDeclaredField("session")
82
- sessionField.isAccessible = true
83
- val sessionValue = sessionField.get(call)
84
-
85
- if (sessionValue != null) {
86
- android.util.Log.d("CallOverlayView", "Session already exists, skipping join")
87
- } else {
88
- android.util.Log.d("CallOverlayView", "No existing session, attempting to join call")
89
- val result = call.join(create = true)
90
- result.onError {
91
- android.util.Log.d("CallOverlayView", "Error joining call")
92
- Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
93
- }
94
- }
95
- } catch (e: Exception) {
96
- android.util.Log.e("CallOverlayView", "Error checking session or joining call", e)
97
- Toast.makeText(context, "Failed to join call: ${e.message}", Toast.LENGTH_LONG).show()
98
- }
99
- }
100
- )
101
-
102
- // Apply VideoTheme
103
- VideoTheme {
104
- // Define required properties.
105
- val allParticipants by call.state.participants.collectAsState()
106
- val remoteParticipants = allParticipants.filter { !it.isLocal }
107
- val remoteParticipantsCount by call.state.participantCounts.collectAsState()
108
- val connection by call.state.connection.collectAsState()
109
- val sessionId by call.state.session.collectAsState()
110
- var parentSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) }
111
-
112
- // Add logging for debugging
113
- LaunchedEffect(allParticipants, remoteParticipants, remoteParticipantsCount, connection, sessionId) {
114
- android.util.Log.d("CallOverlayView", "Detailed State Update:")
115
- android.util.Log.d("CallOverlayView", "- Call ID: ${call.id}")
116
- android.util.Log.d("CallOverlayView", "- Session ID: ${sessionId?.id}")
117
- android.util.Log.d("CallOverlayView", "- All Participants: $allParticipants")
118
- android.util.Log.d("CallOverlayView", "- Remote Participants: $remoteParticipants")
119
- android.util.Log.d("CallOverlayView", "- Remote Participant Count: $remoteParticipantsCount")
120
- android.util.Log.d("CallOverlayView", "- Connection State: $connection")
121
-
122
- // Log each participant's details
123
- allParticipants.forEach { participant ->
124
- android.util.Log.d("CallOverlayView", "Participant Details:")
125
- android.util.Log.d("CallOverlayView", "- ID: ${participant.userId}")
126
- android.util.Log.d("CallOverlayView", "- Is Local: ${participant.isLocal}")
127
- android.util.Log.d("CallOverlayView", "- Has Video: ${participant.videoEnabled}")
128
- android.util.Log.d("CallOverlayView", "- Has Audio: ${participant.audioEnabled}")
129
- }
130
- }
131
-
132
- Box(
133
- contentAlignment = Alignment.Center,
134
- modifier = Modifier
135
- .fillMaxSize()
136
- .background(VideoTheme.colors.baseSenary)
137
- .padding(WindowInsets.safeDrawing.asPaddingValues())
138
- .onSizeChanged { parentSize = it }
139
- ) {
140
- val videoRenderer: @Composable (
141
- modifier: Modifier,
142
- call: Call,
143
- participant: ParticipantState,
144
- style: VideoRendererStyle,
145
- ) -> Unit = { videoModifier, videoCall, videoParticipant, videoStyle ->
146
- ParticipantVideo(
147
- modifier = videoModifier,
148
- call = videoCall,
149
- participant = videoParticipant,
150
- style = videoStyle,
151
- scalingType = VideoScalingType.SCALE_ASPECT_FIT,
152
- actionsContent = { _, _, _ -> }
153
- )
154
- }
155
- val videoRendererNoAction: @Composable (ParticipantState) -> Unit =
156
- { participant ->
157
- ParticipantVideo(
158
- modifier = Modifier
159
- .fillMaxSize()
160
- .clip(VideoTheme.shapes.dialog),
161
- call = call,
162
- participant = participant,
163
- style = RegularVideoRendererStyle(),
164
- scalingType = VideoScalingType.SCALE_ASPECT_FIT,
165
- actionsContent = { _, _, _ -> }
166
- )
167
- }
168
- val floatingVideoRender: @Composable BoxScope.(
169
- call: Call,
170
- parentSize: IntSize
171
- ) -> Unit = { call, _ ->
172
- val participants by call.state.participants.collectAsState()
173
- val me = participants.firstOrNull { it.isLocal }
174
- me?.let { localParticipant ->
175
- val configuration = LocalConfiguration.current
176
- val layoutDirection = LocalLayoutDirection.current
177
- val density = LocalDensity.current
178
- val safeDrawingPadding = WindowInsets.safeDrawing.asPaddingValues()
179
- val adjustedSize = with(density) {
180
- IntSize(
181
- width = (configuration.screenWidthDp.dp.toPx() - safeDrawingPadding.calculateLeftPadding(layoutDirection).toPx() - safeDrawingPadding.calculateRightPadding(layoutDirection).toPx()).toInt(),
182
- height = (configuration.screenHeightDp.dp.toPx() - safeDrawingPadding.calculateTopPadding().toPx() - safeDrawingPadding.calculateBottomPadding().toPx()).toInt()
183
- )
184
- }
185
- FloatingParticipantVideo(
186
- call = call,
187
- videoRenderer = videoRendererNoAction,
188
- participant = if (LocalInspectionMode.current) {
189
- participants.first()
190
- } else {
191
- localParticipant
192
- },
193
- style = RegularVideoRendererStyle(),
194
- parentBounds = adjustedSize,
195
- )
196
- }
197
- }
198
-
199
- if (remoteParticipants.isNotEmpty()) {
200
- android.util.Log.d("CallOverlayView", "Showing ParticipantsLayout with ${remoteParticipants.size} remote participants")
201
- ParticipantsLayout(
202
- modifier = Modifier
203
- .fillMaxSize(),
204
- call = call,
205
- videoRenderer = videoRenderer,
206
- floatingVideoRenderer = floatingVideoRender
207
- )
208
- } else {
209
- Box(
210
- modifier = Modifier
211
- .fillMaxSize()
212
- .background(VideoTheme.colors.baseSenary)
213
- )
214
- }
215
- }
216
- }
217
- }
@@ -1,163 +0,0 @@
1
- package ee.forgr.capacitor.streamcall
2
-
3
- import android.util.Log
4
- import androidx.compose.foundation.background
5
- import androidx.compose.foundation.layout.Arrangement
6
- import androidx.compose.foundation.layout.Box
7
- import androidx.compose.foundation.layout.Column
8
- import androidx.compose.foundation.layout.WindowInsets
9
- import androidx.compose.foundation.layout.asPaddingValues
10
- import androidx.compose.foundation.layout.calculateEndPadding
11
- import androidx.compose.foundation.layout.calculateStartPadding
12
- import androidx.compose.foundation.layout.fillMaxSize
13
- import androidx.compose.foundation.layout.navigationBars
14
- import androidx.compose.foundation.layout.padding
15
- import androidx.compose.foundation.layout.safeDrawing
16
- import androidx.compose.foundation.layout.statusBars
17
- import androidx.compose.runtime.Composable
18
- import androidx.compose.runtime.LaunchedEffect
19
- import androidx.compose.runtime.collectAsState
20
- import androidx.compose.runtime.getValue
21
- import androidx.compose.ui.Alignment
22
- import androidx.compose.ui.Modifier
23
- import androidx.compose.ui.graphics.Color
24
- import androidx.compose.ui.platform.LocalLayoutDirection
25
- import io.getstream.video.android.compose.theme.VideoTheme
26
- import io.getstream.video.android.compose.ui.components.background.CallBackground
27
- import io.getstream.video.android.compose.ui.components.call.ringing.incomingcall.IncomingCallControls
28
- import io.getstream.video.android.compose.ui.components.call.ringing.incomingcall.IncomingCallDetails
29
- import io.getstream.video.android.core.Call
30
- import io.getstream.video.android.core.MemberState
31
- import io.getstream.video.android.core.RingingState
32
- import io.getstream.video.android.core.StreamVideo
33
- import io.getstream.video.android.core.call.state.AcceptCall
34
- import io.getstream.video.android.core.call.state.DeclineCall
35
- import io.getstream.video.android.core.call.state.ToggleCamera
36
- import io.getstream.video.android.model.User
37
-
38
- @Composable
39
- fun IncomingCallView(
40
- streamVideo: StreamVideo?,
41
- call: Call? = null,
42
- onDeclineCall: ((Call) -> Unit)? = null,
43
- onAcceptCall: ((Call) -> Unit)? = null,
44
- onHideIncomingCall: (() -> Unit)? = null
45
- ) {
46
- val ringingState = call?.state?.ringingState?.collectAsState(initial = RingingState.Idle)
47
-
48
- LaunchedEffect(ringingState?.value) {
49
- Log.d("IncomingCallView", "Changing ringingState to ${ringingState?.value}")
50
- when (ringingState?.value) {
51
- RingingState.TimeoutNoAnswer, RingingState.RejectedByAll -> {
52
- Log.d("IncomingCallView", "Call ended (${ringingState.value}), hiding incoming call view")
53
- onHideIncomingCall?.invoke()
54
- }
55
- RingingState.Active -> {
56
- Log.d("IncomingCallView", "Call accepted, hiding incoming call view")
57
- onHideIncomingCall?.invoke()
58
- }
59
- else -> {
60
- // Keep the view visible for other states
61
- }
62
- }
63
- }
64
-
65
- if (ringingState != null) {
66
- Log.d("IncomingCallView", "Ringing state changed to: ${ringingState.value}")
67
- }
68
-
69
- val backgroundColor = when {
70
- streamVideo == null -> Color.Cyan
71
- call == null -> Color.Red
72
- else -> Color.Green
73
- }
74
-
75
- if (call !== null) {
76
- // val participants by call.state.participants.collectAsState()
77
- // val members by call.state.members.collectAsState()
78
- // call.state.session
79
- val session by call.state.session.collectAsState()
80
- val isCameraEnabled by call.camera.isEnabled.collectAsState()
81
- val isVideoType = true
82
-
83
- val statusBarPadding = WindowInsets.statusBars.asPaddingValues()
84
- val navigationBarPadding = WindowInsets.navigationBars.asPaddingValues()
85
- val safeDrawingPadding = WindowInsets.safeDrawing.asPaddingValues()
86
- val layoutDirection = LocalLayoutDirection.current
87
-
88
- // println("participants: ${participants.map { it.name.value }} Members: ${members}")
89
-
90
- VideoTheme {
91
- CallBackground(
92
- modifier = Modifier.fillMaxSize(),
93
- ) {
94
- Column(
95
- modifier = Modifier
96
- .fillMaxSize()
97
- .padding(
98
- top = statusBarPadding.calculateTopPadding(),
99
- bottom = navigationBarPadding.calculateBottomPadding()
100
- ),
101
- horizontalAlignment = Alignment.CenterHorizontally,
102
- verticalArrangement = Arrangement.SpaceBetween
103
- ) {
104
- IncomingCallDetails(
105
- modifier = Modifier
106
- .padding(
107
- top = VideoTheme.dimens.spacingXl,
108
- start = safeDrawingPadding.calculateStartPadding(layoutDirection),
109
- end = safeDrawingPadding.calculateEndPadding(layoutDirection)
110
- ),
111
- isVideoType = isVideoType,
112
- participants = (session?.participants?.map { MemberState(
113
- user = User(
114
- id = it.user.id,
115
- name = it.user.id,
116
- image = it.user.image
117
- ),
118
- custom = mapOf(),
119
- role = it.role,
120
- createdAt = org.threeten.bp.OffsetDateTime.now(),
121
- updatedAt = org.threeten.bp.OffsetDateTime.now(),
122
- deletedAt = org.threeten.bp.OffsetDateTime.now(),
123
- acceptedAt = org.threeten.bp.OffsetDateTime.now(),
124
- rejectedAt = org.threeten.bp.OffsetDateTime.now()
125
- ) }?.filter { it.user.id != streamVideo?.userId }) ?: listOf()
126
- )
127
-
128
- IncomingCallControls(
129
- modifier = Modifier
130
- .padding(
131
- bottom = VideoTheme.dimens.spacingL,
132
- start = safeDrawingPadding.calculateStartPadding(layoutDirection),
133
- end = safeDrawingPadding.calculateEndPadding(layoutDirection)
134
- ),
135
- isVideoCall = isVideoType,
136
- isCameraEnabled = isCameraEnabled,
137
- onCallAction = { action ->
138
- when (action) {
139
- DeclineCall -> {
140
- onDeclineCall?.invoke(call)
141
- }
142
- AcceptCall -> {
143
- call.camera.setEnabled(isCameraEnabled)
144
- onAcceptCall?.invoke(call)
145
- }
146
- is ToggleCamera -> {
147
- call.camera.setEnabled(action.isEnabled)
148
- }
149
- else -> { /* ignore other actions */ }
150
- }
151
- }
152
- )
153
- }
154
- }
155
- }
156
- } else {
157
- Box(
158
- modifier = Modifier
159
- .fillMaxSize()
160
- .background(backgroundColor)
161
- )
162
- }
163
- }