@capgo/capacitor-stream-call 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/Package.swift +31 -0
  2. package/README.md +340 -0
  3. package/StreamCall.podspec +19 -0
  4. package/android/build.gradle +74 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/ee/forgr/capacitor/streamcall/CallOverlayView.kt +281 -0
  7. package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +142 -0
  8. package/android/src/main/java/ee/forgr/capacitor/streamcall/IncomingCallView.kt +147 -0
  9. package/android/src/main/java/ee/forgr/capacitor/streamcall/RingtonePlayer.kt +164 -0
  10. package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +1014 -0
  11. package/android/src/main/java/ee/forgr/capacitor/streamcall/TouchInterceptWrapper.kt +31 -0
  12. package/android/src/main/java/ee/forgr/capacitor/streamcall/UserRepository.kt +111 -0
  13. package/android/src/main/res/.gitkeep +0 -0
  14. package/android/src/main/res/values/strings.xml +7 -0
  15. package/dist/docs.json +533 -0
  16. package/dist/esm/definitions.d.ts +169 -0
  17. package/dist/esm/definitions.js +2 -0
  18. package/dist/esm/definitions.js.map +1 -0
  19. package/dist/esm/index.d.ts +4 -0
  20. package/dist/esm/index.js +7 -0
  21. package/dist/esm/index.js.map +1 -0
  22. package/dist/esm/web.d.ts +32 -0
  23. package/dist/esm/web.js +323 -0
  24. package/dist/esm/web.js.map +1 -0
  25. package/dist/plugin.cjs.js +337 -0
  26. package/dist/plugin.cjs.js.map +1 -0
  27. package/dist/plugin.js +339 -0
  28. package/dist/plugin.js.map +1 -0
  29. package/ios/Sources/StreamCallPlugin/CallOverlayView.swift +147 -0
  30. package/ios/Sources/StreamCallPlugin/CustomCallParticipantImageView.swift +60 -0
  31. package/ios/Sources/StreamCallPlugin/CustomCallView.swift +257 -0
  32. package/ios/Sources/StreamCallPlugin/CustomVideoParticipantsView.swift +107 -0
  33. package/ios/Sources/StreamCallPlugin/ParticipantsView.swift +206 -0
  34. package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +722 -0
  35. package/ios/Sources/StreamCallPlugin/TouchInterceptView.swift +177 -0
  36. package/ios/Sources/StreamCallPlugin/UserRepository.swift +96 -0
  37. package/ios/Sources/StreamCallPlugin/WebviewNavigationDelegate.swift +68 -0
  38. package/ios/Tests/StreamCallPluginTests/StreamCallPluginTests.swift +15 -0
  39. package/package.json +96 -0
@@ -0,0 +1,281 @@
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.Column
9
+ import androidx.compose.foundation.layout.IntrinsicSize
10
+ import androidx.compose.foundation.layout.WindowInsets
11
+ import androidx.compose.foundation.layout.asPaddingValues
12
+ import androidx.compose.foundation.layout.defaultMinSize
13
+ import androidx.compose.foundation.layout.fillMaxSize
14
+ import androidx.compose.foundation.layout.fillMaxWidth
15
+ import androidx.compose.foundation.layout.height
16
+ import androidx.compose.foundation.layout.heightIn
17
+ import androidx.compose.foundation.layout.padding
18
+ import androidx.compose.foundation.layout.safeDrawing
19
+ import androidx.compose.foundation.layout.width
20
+ import androidx.compose.foundation.layout.wrapContentHeight
21
+ import androidx.compose.foundation.lazy.LazyColumn
22
+ import androidx.compose.foundation.lazy.items
23
+ import androidx.compose.material3.Text
24
+ import androidx.compose.runtime.Composable
25
+ import androidx.compose.runtime.DisposableEffect
26
+ import androidx.compose.runtime.LaunchedEffect
27
+ import androidx.compose.runtime.collectAsState
28
+ import androidx.compose.runtime.getValue
29
+ import androidx.compose.runtime.mutableStateOf
30
+ import androidx.compose.runtime.remember
31
+ import androidx.compose.runtime.setValue
32
+ import androidx.compose.ui.Alignment
33
+ import androidx.compose.ui.Modifier
34
+ import androidx.compose.ui.draw.clip
35
+ import androidx.compose.ui.layout.ContentScale
36
+ import androidx.compose.ui.layout.onSizeChanged
37
+ import androidx.compose.ui.platform.LocalConfiguration
38
+ import androidx.compose.ui.platform.LocalInspectionMode
39
+ import androidx.compose.ui.platform.LocalLayoutDirection
40
+ import androidx.compose.ui.platform.LocalDensity
41
+ import androidx.compose.ui.text.style.TextAlign
42
+ import androidx.compose.ui.unit.IntSize
43
+ import androidx.compose.ui.unit.dp
44
+ import androidx.compose.ui.unit.sp
45
+ import io.getstream.video.android.compose.permission.LaunchCallPermissions
46
+ import io.getstream.video.android.compose.theme.VideoTheme
47
+ import io.getstream.video.android.compose.ui.components.call.renderer.FloatingParticipantVideo
48
+ import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantVideo
49
+ import io.getstream.video.android.compose.ui.components.call.renderer.ParticipantsLayout
50
+ import io.getstream.video.android.compose.ui.components.call.renderer.RegularVideoRendererStyle
51
+ import io.getstream.video.android.compose.ui.components.call.renderer.VideoRendererStyle
52
+ import io.getstream.video.android.compose.ui.components.call.renderer.copy
53
+ import io.getstream.video.android.core.GEO
54
+ import io.getstream.video.android.core.ParticipantState
55
+ import io.getstream.video.android.core.RealtimeConnection
56
+ import io.getstream.video.android.core.StreamVideo
57
+ import io.getstream.video.android.core.StreamVideoBuilder
58
+ import io.getstream.video.android.model.User
59
+ import io.getstream.video.android.core.Call
60
+ import io.getstream.video.android.compose.ui.components.video.VideoRenderer
61
+ import io.getstream.video.android.compose.ui.components.video.VideoScalingType
62
+ import io.getstream.video.android.compose.ui.components.video.config.VideoRendererConfig
63
+ import stream.video.sfu.models.TrackType
64
+ import androidx.compose.ui.graphics.Color
65
+
66
+ @Composable
67
+ private fun ParticipantVideoView(
68
+ call: Call,
69
+ participant: ParticipantState,
70
+ parentSize: IntSize,
71
+ onVisibilityChanged: ((ParticipantState, Boolean) -> Unit)? = null
72
+ ) {
73
+ LaunchedEffect(participant) {
74
+ onVisibilityChanged?.invoke(participant, true)
75
+ }
76
+
77
+ DisposableEffect(participant) {
78
+ onDispose {
79
+ onVisibilityChanged?.invoke(participant, false)
80
+ }
81
+ }
82
+
83
+ Box(
84
+ modifier = Modifier
85
+ .fillMaxWidth()
86
+ .height(300.dp)
87
+ .background(VideoTheme.colors.baseSenary),
88
+ contentAlignment = Alignment.Center
89
+ ) {
90
+ ParticipantVideo(
91
+ modifier = Modifier
92
+ .fillMaxWidth()
93
+ .height(IntrinsicSize.Min),
94
+ call = call,
95
+ participant = participant
96
+ )
97
+ }
98
+ }
99
+
100
+ @Composable
101
+ fun CallOverlayView(
102
+ context: Context,
103
+ streamVideo: StreamVideo?,
104
+ call: Call?
105
+ ) {
106
+ if (streamVideo == null) {
107
+ Box(
108
+ modifier = Modifier
109
+ .fillMaxSize()
110
+ .background(Color.Red)
111
+ )
112
+ return
113
+ }
114
+
115
+ // Collect the active call state
116
+ //val activeCall by internalInstance.state.activeCall.collectAsState()
117
+ // val call = activeCall
118
+ if (call == null) {
119
+ Box(
120
+ modifier = Modifier
121
+ .fillMaxSize()
122
+ .background(Color.LightGray)
123
+ )
124
+ return
125
+ }
126
+
127
+ // Handle permissions in the Composable context
128
+ LaunchCallPermissions(
129
+ call = call,
130
+ onAllPermissionsGranted = {
131
+ try {
132
+ // Check session using reflection before joining
133
+ val callClass = call.javaClass
134
+ val sessionField = callClass.getDeclaredField("session")
135
+ sessionField.isAccessible = true
136
+ val sessionValue = sessionField.get(call)
137
+
138
+ if (sessionValue != null) {
139
+ android.util.Log.d("CallOverlayView", "Session already exists, skipping join")
140
+ } else {
141
+ android.util.Log.d("CallOverlayView", "No existing session, attempting to join call")
142
+ val result = call.join(create = true)
143
+ result.onError {
144
+ android.util.Log.d("CallOverlayView", "Error joining call")
145
+ Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
146
+ }
147
+ }
148
+ } catch (e: Exception) {
149
+ android.util.Log.e("CallOverlayView", "Error checking session or joining call", e)
150
+ Toast.makeText(context, "Failed to join call: ${e.message}", Toast.LENGTH_LONG).show()
151
+ }
152
+ }
153
+ )
154
+
155
+ // Apply VideoTheme
156
+ VideoTheme {
157
+ // Define required properties.
158
+ val allParticipants by call.state.participants.collectAsState()
159
+ val remoteParticipants = allParticipants.filter { !it.isLocal }
160
+ val remoteParticipantsCount by call.state.participantCounts.collectAsState()
161
+ val connection by call.state.connection.collectAsState()
162
+ val sessionId by call.state.session.collectAsState()
163
+ var parentSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) }
164
+
165
+ // Add logging for debugging
166
+ LaunchedEffect(allParticipants, remoteParticipants, remoteParticipantsCount, connection, sessionId) {
167
+ android.util.Log.d("CallOverlayView", "Detailed State Update:")
168
+ android.util.Log.d("CallOverlayView", "- Call ID: ${call.id}")
169
+ android.util.Log.d("CallOverlayView", "- Session ID: ${sessionId?.id}")
170
+ android.util.Log.d("CallOverlayView", "- All Participants: $allParticipants")
171
+ android.util.Log.d("CallOverlayView", "- Remote Participants: $remoteParticipants")
172
+ android.util.Log.d("CallOverlayView", "- Remote Participant Count: $remoteParticipantsCount")
173
+ android.util.Log.d("CallOverlayView", "- Connection State: $connection")
174
+
175
+ // Log each participant's details
176
+ allParticipants.forEach { participant ->
177
+ android.util.Log.d("CallOverlayView", "Participant Details:")
178
+ android.util.Log.d("CallOverlayView", "- ID: ${participant.userId}")
179
+ android.util.Log.d("CallOverlayView", "- Is Local: ${participant.isLocal}")
180
+ android.util.Log.d("CallOverlayView", "- Has Video: ${participant.videoEnabled}")
181
+ android.util.Log.d("CallOverlayView", "- Has Audio: ${participant.audioEnabled}")
182
+ }
183
+ }
184
+
185
+ Box(
186
+ contentAlignment = Alignment.Center,
187
+ modifier = Modifier
188
+ .fillMaxSize()
189
+ .background(VideoTheme.colors.baseSenary)
190
+ .padding(WindowInsets.safeDrawing.asPaddingValues())
191
+ .onSizeChanged { parentSize = it }
192
+ ) {
193
+ val videoRenderer: @Composable (
194
+ modifier: Modifier,
195
+ call: Call,
196
+ participant: ParticipantState,
197
+ style: VideoRendererStyle,
198
+ ) -> Unit = { videoModifier, videoCall, videoParticipant, videoStyle ->
199
+ ParticipantVideo(
200
+ modifier = videoModifier,
201
+ call = videoCall,
202
+ participant = videoParticipant,
203
+ style = videoStyle,
204
+ scalingType = VideoScalingType.SCALE_ASPECT_FIT,
205
+ actionsContent = { _, _, _ -> }
206
+ )
207
+ }
208
+ val videoRendererNoAction: @Composable (ParticipantState) -> Unit =
209
+ { participant ->
210
+ ParticipantVideo(
211
+ modifier = Modifier
212
+ .fillMaxSize()
213
+ .clip(VideoTheme.shapes.dialog),
214
+ call = call,
215
+ participant = participant,
216
+ style = RegularVideoRendererStyle(),
217
+ scalingType = VideoScalingType.SCALE_ASPECT_FIT,
218
+ actionsContent = { _, _, _ -> }
219
+ )
220
+ }
221
+ val floatingVideoRender: @Composable BoxScope.(
222
+ call: Call,
223
+ parentSize: IntSize
224
+ ) -> Unit = { call, _ ->
225
+ val participants by call.state.participants.collectAsState()
226
+ val me = participants.firstOrNull { it.isLocal }
227
+ me?.let { localParticipant ->
228
+ val configuration = LocalConfiguration.current
229
+ val layoutDirection = LocalLayoutDirection.current
230
+ val density = LocalDensity.current
231
+ val safeDrawingPadding = WindowInsets.safeDrawing.asPaddingValues()
232
+ val adjustedSize = with(density) {
233
+ IntSize(
234
+ width = (configuration.screenWidthDp.dp.toPx() - safeDrawingPadding.calculateLeftPadding(layoutDirection).toPx() - safeDrawingPadding.calculateRightPadding(layoutDirection).toPx()).toInt(),
235
+ height = (configuration.screenHeightDp.dp.toPx() - safeDrawingPadding.calculateTopPadding().toPx() - safeDrawingPadding.calculateBottomPadding().toPx()).toInt()
236
+ )
237
+ }
238
+ FloatingParticipantVideo(
239
+ call = call,
240
+ videoRenderer = videoRendererNoAction,
241
+ participant = if (LocalInspectionMode.current) {
242
+ participants.first()
243
+ } else {
244
+ localParticipant
245
+ },
246
+ style = RegularVideoRendererStyle(),
247
+ parentBounds = adjustedSize,
248
+ )
249
+ }
250
+ }
251
+
252
+ if (remoteParticipants.isNotEmpty()) {
253
+ android.util.Log.d("CallOverlayView", "Showing ParticipantsLayout with ${remoteParticipants.size} remote participants")
254
+ ParticipantsLayout(
255
+ modifier = Modifier
256
+ .fillMaxSize(),
257
+ call = call,
258
+ videoRenderer = videoRenderer,
259
+ floatingVideoRenderer = floatingVideoRender
260
+ )
261
+ } else {
262
+ if (connection != RealtimeConnection.Connected) {
263
+ android.util.Log.d("CallOverlayView", "Showing waiting message - not connected")
264
+ Text(
265
+ text = "waiting for a remote participant...",
266
+ fontSize = 30.sp,
267
+ color = VideoTheme.colors.basePrimary
268
+ )
269
+ } else {
270
+ Text(
271
+ modifier = Modifier.padding(30.dp),
272
+ text = "Join call ${call.id} in your browser to see the video here",
273
+ fontSize = 30.sp,
274
+ color = VideoTheme.colors.basePrimary,
275
+ textAlign = TextAlign.Center
276
+ )
277
+ }
278
+ }
279
+ }
280
+ }
281
+ }
@@ -0,0 +1,142 @@
1
+ package ee.forgr.capacitor.streamcall
2
+
3
+ import android.app.Application
4
+ import android.app.Notification
5
+ import android.app.NotificationManager
6
+ import android.app.PendingIntent
7
+ import android.content.Context
8
+ import android.content.Intent
9
+ import android.media.RingtoneManager
10
+ import android.os.Build
11
+ import androidx.core.app.NotificationCompat
12
+ import io.getstream.video.android.core.notifications.DefaultNotificationHandler
13
+ import io.getstream.video.android.model.StreamCallId
14
+
15
+ class CustomNotificationHandler(
16
+ val application: Application,
17
+ private val endCall: (callId: StreamCallId) -> Unit = {},
18
+ private val incomingCall: () -> Unit = {}
19
+ ) : DefaultNotificationHandler(application, hideRingingNotificationInForeground = false) {
20
+ companion object {
21
+ private const val PREFS_NAME = "StreamCallPrefs"
22
+ private const val KEY_NOTIFICATION_TIME = "notification_creation_time"
23
+ }
24
+ var allowSound = true;
25
+
26
+ override fun getIncomingCallNotification(
27
+ fullScreenPendingIntent: PendingIntent,
28
+ acceptCallPendingIntent: PendingIntent,
29
+ rejectCallPendingIntent: PendingIntent,
30
+ callerName: String?,
31
+ shouldHaveContentIntent: Boolean,
32
+ ): Notification {
33
+ val showAsHighPriority = true
34
+ val channelId = "incoming_calls_custom"
35
+
36
+ customCreateIncomingCallChannel(channelId, showAsHighPriority)
37
+
38
+ return buildNotification(
39
+ fullScreenPendingIntent,
40
+ acceptCallPendingIntent,
41
+ rejectCallPendingIntent,
42
+ callerName,
43
+ shouldHaveContentIntent,
44
+ channelId,
45
+ true // Include sound
46
+ )
47
+ }
48
+
49
+ fun buildNotification(
50
+ fullScreenPendingIntent: PendingIntent,
51
+ acceptCallPendingIntent: PendingIntent,
52
+ rejectCallPendingIntent: PendingIntent,
53
+ callerName: String?,
54
+ shouldHaveContentIntent: Boolean,
55
+ channelId: String,
56
+ includeSound: Boolean
57
+ ): Notification {
58
+ return getNotification {
59
+ priority = NotificationCompat.PRIORITY_HIGH
60
+ setContentTitle(callerName)
61
+ setContentText("Incoming call")
62
+ setChannelId(channelId)
63
+ setOngoing(true)
64
+ setAutoCancel(false)
65
+ setCategory(NotificationCompat.CATEGORY_CALL)
66
+
67
+ // Clear all defaults first
68
+ setDefaults(0)
69
+
70
+ if (includeSound && allowSound) {
71
+ setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))
72
+ setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
73
+ } else {
74
+ setSound(null)
75
+ setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
76
+ }
77
+
78
+ setVibrate(longArrayOf(0, 1000, 500, 1000))
79
+ setLights(0xFF0000FF.toInt(), 1000, 1000)
80
+ setFullScreenIntent(fullScreenPendingIntent, true)
81
+ if (shouldHaveContentIntent) {
82
+ setContentIntent(fullScreenPendingIntent)
83
+ } else {
84
+ val emptyIntent = PendingIntent.getActivity(
85
+ application,
86
+ 0,
87
+ Intent(),
88
+ PendingIntent.FLAG_IMMUTABLE,
89
+ )
90
+ setContentIntent(emptyIntent)
91
+ }
92
+ addCallActions(acceptCallPendingIntent, rejectCallPendingIntent, callerName)
93
+ }.apply {
94
+ // flags = flags or NotificationCompat.FLAG_ONGOING_EVENT
95
+ }
96
+ }
97
+
98
+ override fun onMissedCall(callId: StreamCallId, callDisplayName: String) {
99
+ endCall(callId)
100
+ super.onMissedCall(callId, callDisplayName)
101
+ }
102
+
103
+ open fun customCreateIncomingCallChannel(channelId: String, showAsHighPriority: Boolean) {
104
+ maybeCreateChannel(
105
+ channelId = channelId,
106
+ context = application,
107
+ configure = {
108
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
109
+ name = application.getString(
110
+ R.string.stream_video_incoming_call_notification_channel_title,
111
+ )
112
+ description = application.getString(
113
+ if (showAsHighPriority) {
114
+ R.string.stream_video_incoming_call_notification_channel_description
115
+ } else {
116
+ R.string.stream_video_incoming_call_low_priority_notification_channel_description
117
+ },
118
+ )
119
+ importance = if (showAsHighPriority) {
120
+ NotificationManager.IMPORTANCE_HIGH
121
+ } else {
122
+ NotificationManager.IMPORTANCE_LOW
123
+ }
124
+ this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
125
+ this.setShowBadge(true)
126
+
127
+ // Set the channel to be silent since we handle sound via RingtonePlayer
128
+ setSound(null, null)
129
+ enableVibration(true)
130
+ enableLights(true)
131
+ }
132
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
133
+ this.setAllowBubbles(true)
134
+ }
135
+ },
136
+ )
137
+ }
138
+
139
+ public fun clone(): CustomNotificationHandler {
140
+ return CustomNotificationHandler(this.application, this.endCall, this.incomingCall)
141
+ }
142
+ }
@@ -0,0 +1,147 @@
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.LocalContext
25
+ import androidx.compose.ui.platform.LocalLayoutDirection
26
+ import io.getstream.video.android.compose.theme.VideoTheme
27
+ import io.getstream.video.android.compose.ui.components.background.CallBackground
28
+ import io.getstream.video.android.compose.ui.components.call.ringing.incomingcall.IncomingCallControls
29
+ import io.getstream.video.android.compose.ui.components.call.ringing.incomingcall.IncomingCallDetails
30
+ import io.getstream.video.android.core.Call
31
+ import io.getstream.video.android.core.MemberState
32
+ import io.getstream.video.android.core.RingingState
33
+ import io.getstream.video.android.core.StreamVideo
34
+ import io.getstream.video.android.core.call.state.AcceptCall
35
+ import io.getstream.video.android.core.call.state.DeclineCall
36
+ import io.getstream.video.android.model.User
37
+ import java.time.OffsetDateTime
38
+
39
+ @Composable
40
+ fun IncomingCallView(
41
+ streamVideo: StreamVideo?,
42
+ call: Call? = null,
43
+ onDeclineCall: ((Call) -> Unit)? = null,
44
+ onAcceptCall: ((Call) -> Unit)? = null,
45
+ onHideIncomingCall: (() -> Unit)? = null
46
+ ) {
47
+ val ringingState = call?.state?.ringingState?.collectAsState(initial = RingingState.Idle)
48
+
49
+ LaunchedEffect(ringingState?.value) {
50
+ Log.d("IncomingCallView", "Changing ringingState to $ringingState?.value")
51
+ if (ringingState?.value == RingingState.TimeoutNoAnswer || ringingState?.value == RingingState.RejectedByAll) {
52
+ Log.d("IncomingCallView", "Call timed out, hiding incoming call view")
53
+ onHideIncomingCall?.invoke()
54
+ }
55
+ }
56
+
57
+ if (ringingState != null) {
58
+ Log.d("IncomingCallView", "Ringing state changed to: ${ringingState.value}")
59
+ }
60
+
61
+ val backgroundColor = when {
62
+ streamVideo == null -> Color.Cyan
63
+ call == null -> Color.Red
64
+ else -> Color.Green
65
+ }
66
+
67
+ if (call !== null) {
68
+ // val participants by call.state.participants.collectAsState()
69
+ // val members by call.state.members.collectAsState()
70
+ // call.state.session
71
+ val session by call.state.session.collectAsState()
72
+ val isCameraEnabled by call.camera.isEnabled.collectAsState()
73
+ val isVideoType = true
74
+
75
+ val statusBarPadding = WindowInsets.statusBars.asPaddingValues()
76
+ val navigationBarPadding = WindowInsets.navigationBars.asPaddingValues()
77
+ val safeDrawingPadding = WindowInsets.safeDrawing.asPaddingValues()
78
+ val layoutDirection = LocalLayoutDirection.current
79
+
80
+ // println("participants: ${participants.map { it.name.value }} Members: ${members}")
81
+
82
+ VideoTheme {
83
+ CallBackground(
84
+ modifier = Modifier.fillMaxSize(),
85
+ ) {
86
+ Column(
87
+ modifier = Modifier
88
+ .fillMaxSize()
89
+ .padding(
90
+ top = statusBarPadding.calculateTopPadding(),
91
+ bottom = navigationBarPadding.calculateBottomPadding()
92
+ ),
93
+ horizontalAlignment = Alignment.CenterHorizontally,
94
+ verticalArrangement = Arrangement.SpaceBetween
95
+ ) {
96
+ IncomingCallDetails(
97
+ modifier = Modifier
98
+ .padding(
99
+ top = VideoTheme.dimens.spacingXl,
100
+ start = safeDrawingPadding.calculateStartPadding(layoutDirection),
101
+ end = safeDrawingPadding.calculateEndPadding(layoutDirection)
102
+ ),
103
+ isVideoType = isVideoType,
104
+ participants = (session?.participants?.map { MemberState(
105
+ user = User(
106
+ id = it.user.id,
107
+ name = it.user.id,
108
+ image = it.user.image
109
+ ),
110
+ custom = mapOf(),
111
+ role = it.role,
112
+ createdAt = org.threeten.bp.OffsetDateTime.now(),
113
+ updatedAt = org.threeten.bp.OffsetDateTime.now(),
114
+ deletedAt = org.threeten.bp.OffsetDateTime.now(),
115
+ acceptedAt = org.threeten.bp.OffsetDateTime.now(),
116
+ rejectedAt = org.threeten.bp.OffsetDateTime.now()
117
+ ) }?.filter { it.user.id != streamVideo?.userId }) ?: listOf()
118
+ )
119
+
120
+ IncomingCallControls(
121
+ modifier = Modifier
122
+ .padding(
123
+ bottom = VideoTheme.dimens.spacingL,
124
+ start = safeDrawingPadding.calculateStartPadding(layoutDirection),
125
+ end = safeDrawingPadding.calculateEndPadding(layoutDirection)
126
+ ),
127
+ isVideoCall = isVideoType,
128
+ isCameraEnabled = isCameraEnabled,
129
+ onCallAction = { action ->
130
+ when (action) {
131
+ DeclineCall -> onDeclineCall?.invoke(call)
132
+ AcceptCall -> onAcceptCall?.invoke(call)
133
+ else -> { /* ignore other actions */ }
134
+ }
135
+ }
136
+ )
137
+ }
138
+ }
139
+ }
140
+ } else {
141
+ Box(
142
+ modifier = Modifier
143
+ .fillMaxSize()
144
+ .background(backgroundColor)
145
+ )
146
+ }
147
+ }