@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.
- package/README.md +64 -0
- package/android/build.gradle +6 -6
- package/android/src/main/AndroidManifest.xml +29 -0
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +69 -38
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallBackgroundService.java +41 -0
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallFragment.kt +56 -0
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +506 -169
- package/android/src/main/java/ee/forgr/capacitor/streamcall/TouchInterceptWrapper.kt +8 -8
- package/dist/docs.json +125 -0
- package/dist/esm/definitions.d.ts +39 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +6 -0
- package/dist/esm/web.js +21 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +21 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +21 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +77 -160
- package/package.json +1 -1
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CallOverlayView.kt +0 -217
- package/android/src/main/java/ee/forgr/capacitor/streamcall/IncomingCallView.kt +0 -163
|
@@ -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,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
|
-
}
|