@capgo/capacitor-stream-call 0.0.87 → 0.0.89
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 +24 -0
- package/android/build.gradle +2 -0
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomStreamIntentResolver.kt +161 -0
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +90 -59
- package/android/src/main/java/ee/forgr/capacitor/streamcall/UserRepository.kt +14 -2
- package/dist/docs.json +83 -0
- package/dist/esm/definitions.d.ts +26 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -1
- package/dist/esm/web.js +25 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +25 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +25 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +63 -5
- package/ios/Sources/StreamCallPlugin/UserRepository.swift +15 -1
- package/package.json +1 -1
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +0 -323
package/README.md
CHANGED
|
@@ -254,6 +254,7 @@ export class CallService {
|
|
|
254
254
|
* [`getCallInfo(...)`](#getcallinfo)
|
|
255
255
|
* [`setDynamicStreamVideoApikey(...)`](#setdynamicstreamvideoapikey)
|
|
256
256
|
* [`getDynamicStreamVideoApikey()`](#getdynamicstreamvideoapikey)
|
|
257
|
+
* [`getCurrentUser()`](#getcurrentuser)
|
|
257
258
|
* [Interfaces](#interfaces)
|
|
258
259
|
* [Type Aliases](#type-aliases)
|
|
259
260
|
* [Enums](#enums)
|
|
@@ -538,6 +539,19 @@ Get the currently set dynamic Stream Video API key
|
|
|
538
539
|
--------------------
|
|
539
540
|
|
|
540
541
|
|
|
542
|
+
### getCurrentUser()
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
getCurrentUser() => Promise<CurrentUserResponse>
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Get the current user's information
|
|
549
|
+
|
|
550
|
+
**Returns:** <code>Promise<<a href="#currentuserresponse">CurrentUserResponse</a>></code>
|
|
551
|
+
|
|
552
|
+
--------------------
|
|
553
|
+
|
|
554
|
+
|
|
541
555
|
### Interfaces
|
|
542
556
|
|
|
543
557
|
|
|
@@ -819,6 +833,16 @@ The JSON representation for <a href="#listvalue">`ListValue`</a> is JSON array.
|
|
|
819
833
|
| **`hasDynamicKey`** | <code>boolean</code> | Whether a dynamic key is currently set |
|
|
820
834
|
|
|
821
835
|
|
|
836
|
+
#### CurrentUserResponse
|
|
837
|
+
|
|
838
|
+
| Prop | Type | Description |
|
|
839
|
+
| ---------------- | -------------------- | --------------------------------------- |
|
|
840
|
+
| **`userId`** | <code>string</code> | User ID of the current user |
|
|
841
|
+
| **`name`** | <code>string</code> | Display name of the current user |
|
|
842
|
+
| **`imageURL`** | <code>string</code> | Avatar URL of the current user |
|
|
843
|
+
| **`isLoggedIn`** | <code>boolean</code> | Whether the user is currently logged in |
|
|
844
|
+
|
|
845
|
+
|
|
822
846
|
### Type Aliases
|
|
823
847
|
|
|
824
848
|
|
package/android/build.gradle
CHANGED
|
@@ -73,6 +73,8 @@ dependencies {
|
|
|
73
73
|
implementation "androidx.compose.foundation:foundation:$compose_version"
|
|
74
74
|
implementation "androidx.compose.runtime:runtime:$compose_version"
|
|
75
75
|
implementation "androidx.compose.material3:material3:1.3.2"
|
|
76
|
+
implementation 'androidx.media:media:1.7.0'
|
|
77
|
+
|
|
76
78
|
|
|
77
79
|
// Stream dependencies
|
|
78
80
|
implementation("io.getstream:stream-video-android-ui-compose:1.9.2")
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
package ee.forgr.capacitor.streamcall
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import android.app.PendingIntent
|
|
5
|
+
import android.content.ComponentName
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.content.pm.ResolveInfo
|
|
8
|
+
import io.getstream.log.taggedLogger
|
|
9
|
+
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
10
|
+
import io.getstream.video.android.core.notifications.StreamIntentResolver
|
|
11
|
+
import io.getstream.video.android.model.StreamCallId
|
|
12
|
+
|
|
13
|
+
class CustomStreamIntentResolver(private val context: Application) : StreamIntentResolver {
|
|
14
|
+
|
|
15
|
+
private val logger by taggedLogger("CustomIntentResolver")
|
|
16
|
+
private val PENDING_INTENT_FLAG = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
17
|
+
|
|
18
|
+
override fun searchIncomingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
19
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
20
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
21
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
22
|
+
action = "io.getstream.video.android.action.INCOMING_CALL"
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return PendingIntent.getActivity(
|
|
27
|
+
context,
|
|
28
|
+
callId.cid.hashCode(),
|
|
29
|
+
launchIntent,
|
|
30
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun searchOutgoingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
35
|
+
// For outgoing calls, create a specific intent that only opens webview when user taps
|
|
36
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
37
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
38
|
+
putExtra("callCid", callId.cid)
|
|
39
|
+
putExtra("action", "outgoing_call_tap") // Different action to distinguish from automatic events
|
|
40
|
+
putExtra("openWebview", true)
|
|
41
|
+
putExtra("fromNotification", true)
|
|
42
|
+
putExtra("userTapped", true) // Explicitly mark this as user-initiated
|
|
43
|
+
action = "ee.forgr.capacitor.streamcall.OUTGOING_CALL_TAP.${callId.cid}"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return PendingIntent.getActivity(
|
|
47
|
+
context,
|
|
48
|
+
callId.cid.hashCode(),
|
|
49
|
+
launchIntent,
|
|
50
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun searchNotificationCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
55
|
+
searchActivityPendingIntent(Intent(NotificationHandler.ACTION_NOTIFICATION), callId, notificationId)
|
|
56
|
+
|
|
57
|
+
override fun searchMissedCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
58
|
+
searchActivityPendingIntent(Intent(NotificationHandler.ACTION_MISSED_CALL), callId, notificationId)
|
|
59
|
+
|
|
60
|
+
override fun getDefaultPendingIntent(): PendingIntent {
|
|
61
|
+
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
62
|
+
?: Intent(Intent.ACTION_MAIN).apply {
|
|
63
|
+
setPackage(context.packageName)
|
|
64
|
+
addCategory(Intent.CATEGORY_LAUNCHER)
|
|
65
|
+
}
|
|
66
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
67
|
+
return PendingIntent.getActivity(
|
|
68
|
+
context,
|
|
69
|
+
0,
|
|
70
|
+
intent,
|
|
71
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun searchLiveCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
76
|
+
searchActivityPendingIntent(Intent(NotificationHandler.ACTION_LIVE_CALL), callId, notificationId)
|
|
77
|
+
|
|
78
|
+
override fun searchAcceptCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
79
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
80
|
+
action = NotificationHandler.ACTION_ACCEPT_CALL
|
|
81
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
82
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return PendingIntent.getActivity(
|
|
86
|
+
context,
|
|
87
|
+
callId.cid.hashCode() + 10,
|
|
88
|
+
launchIntent,
|
|
89
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override fun searchRejectCallPendingIntent(callId: StreamCallId): PendingIntent? =
|
|
94
|
+
searchBroadcastPendingIntent(Intent(NotificationHandler.ACTION_REJECT_CALL), callId)
|
|
95
|
+
|
|
96
|
+
override fun searchEndCallPendingIntent(callId: StreamCallId): PendingIntent? {
|
|
97
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
98
|
+
action = NotificationHandler.ACTION_LEAVE_CALL
|
|
99
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
100
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return PendingIntent.getActivity(
|
|
104
|
+
context,
|
|
105
|
+
callId.cid.hashCode() + 30,
|
|
106
|
+
launchIntent,
|
|
107
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun searchOngoingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
112
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
113
|
+
action = NotificationHandler.ACTION_ONGOING_CALL
|
|
114
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
115
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return PendingIntent.getActivity(
|
|
119
|
+
context,
|
|
120
|
+
callId.cid.hashCode() + 20,
|
|
121
|
+
launchIntent,
|
|
122
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private fun searchBroadcastPendingIntent(baseIntent: Intent, callId: StreamCallId): PendingIntent? =
|
|
127
|
+
searchResolveInfo { context.packageManager.queryBroadcastReceivers(baseIntent, 0) }?.let {
|
|
128
|
+
getBroadcastForIntent(baseIntent, it, callId)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun searchActivityPendingIntent(baseIntent: Intent, callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
132
|
+
searchResolveInfo { context.packageManager.queryIntentActivities(baseIntent, 0) }?.let {
|
|
133
|
+
getActivityForIntent(baseIntent, it, callId, notificationId)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun searchResolveInfo(availableComponents: () -> List<ResolveInfo>): ResolveInfo? =
|
|
137
|
+
availableComponents()
|
|
138
|
+
.filter { it.activityInfo.packageName == context.packageName }
|
|
139
|
+
.maxByOrNull { it.priority }
|
|
140
|
+
|
|
141
|
+
private fun getActivityForIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId, notificationId: Int, flags: Int = PENDING_INTENT_FLAG): PendingIntent {
|
|
142
|
+
return PendingIntent.getActivity(
|
|
143
|
+
context,
|
|
144
|
+
notificationId,
|
|
145
|
+
buildComponentIntent(baseIntent, resolveInfo, callId),
|
|
146
|
+
flags
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private fun getBroadcastForIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId, flags: Int = PENDING_INTENT_FLAG): PendingIntent {
|
|
151
|
+
return PendingIntent.getBroadcast(context, 0, buildComponentIntent(baseIntent, resolveInfo, callId), flags)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private fun buildComponentIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId): Intent {
|
|
155
|
+
return Intent(baseIntent).apply {
|
|
156
|
+
component = ComponentName(resolveInfo.activityInfo.applicationInfo.packageName, resolveInfo.activityInfo.name)
|
|
157
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
158
|
+
putExtra(NotificationHandler.INTENT_EXTRA_NOTIFICATION_ID, NotificationHandler.INCOMING_CALL_NOTIFICATION_ID)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -73,6 +73,10 @@ import io.getstream.video.android.core.events.ParticipantLeftEvent
|
|
|
73
73
|
import io.getstream.video.android.core.internal.InternalStreamVideoApi
|
|
74
74
|
import io.getstream.video.android.core.notifications.NotificationConfig
|
|
75
75
|
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
76
|
+
import io.getstream.video.android.core.notifications.handlers.CompatibilityStreamNotificationHandler
|
|
77
|
+
import io.getstream.video.android.core.notifications.handlers.StreamNotificationBuilderInterceptors
|
|
78
|
+
import androidx.core.app.NotificationCompat
|
|
79
|
+
import android.app.PendingIntent
|
|
76
80
|
import io.getstream.video.android.core.sounds.RingingConfig
|
|
77
81
|
import io.getstream.video.android.core.sounds.toSounds
|
|
78
82
|
import io.getstream.video.android.model.Device
|
|
@@ -196,16 +200,20 @@ class StreamCallPlugin : Plugin() {
|
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
override fun load() {
|
|
203
|
+
Log.d("StreamCallPlugin", "Plugin load() called")
|
|
199
204
|
try {
|
|
200
205
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
|
201
206
|
if (packageInfo.firstInstallTime == packageInfo.lastUpdateTime) {
|
|
202
207
|
Log.d("StreamCallPlugin", "Fresh install detected, clearing user credentials.")
|
|
203
208
|
SecureUserRepository.getInstance(context).removeCurrentUser()
|
|
209
|
+
} else {
|
|
210
|
+
Log.d("StreamCallPlugin", "App update or existing installation detected")
|
|
204
211
|
}
|
|
205
212
|
} catch (e: Exception) {
|
|
206
213
|
Log.e("StreamCallPlugin", "Error checking for fresh install", e)
|
|
207
214
|
}
|
|
208
215
|
// general init
|
|
216
|
+
Log.d("StreamCallPlugin", "Calling initializeStreamVideo() from load()")
|
|
209
217
|
initializeStreamVideo()
|
|
210
218
|
setupViews()
|
|
211
219
|
super.load()
|
|
@@ -316,6 +324,12 @@ class StreamCallPlugin : Plugin() {
|
|
|
316
324
|
Log.e("StreamCallPlugin", "handleOnNewIntent: ACCEPT_CALL - Call object is null for cid: $cid")
|
|
317
325
|
}
|
|
318
326
|
}
|
|
327
|
+
} else if (action === NotificationHandler.ACTION_LEAVE_CALL) {
|
|
328
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: Matched LEAVE_CALL action")
|
|
329
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
330
|
+
val success = _endCall()
|
|
331
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: LEAVE_CALL - End call result: $success")
|
|
332
|
+
}
|
|
319
333
|
}
|
|
320
334
|
// Log the intent information
|
|
321
335
|
Log.d("StreamCallPlugin", "New Intent - Action: $action")
|
|
@@ -567,47 +581,6 @@ class StreamCallPlugin : Plugin() {
|
|
|
567
581
|
// unsafe cast, add better handling
|
|
568
582
|
val application = contextToUse.applicationContext as Application
|
|
569
583
|
Log.d("StreamCallPlugin", "No existing StreamVideo singleton client, creating new one")
|
|
570
|
-
val notificationHandler = CustomNotificationHandler(
|
|
571
|
-
application = application,
|
|
572
|
-
endCall = { callId ->
|
|
573
|
-
val activeCall = streamVideoClient?.call(callId.type, callId.id)
|
|
574
|
-
|
|
575
|
-
kotlinx.coroutines.GlobalScope.launch {
|
|
576
|
-
try {
|
|
577
|
-
Log.i(
|
|
578
|
-
"StreamCallPlugin",
|
|
579
|
-
"Attempt to endCallRaw, activeCall == null: ${activeCall == null}",
|
|
580
|
-
)
|
|
581
|
-
activeCall?.let { endCallRaw(it) }
|
|
582
|
-
} catch (e: Exception) {
|
|
583
|
-
Log.e(
|
|
584
|
-
"StreamCallPlugin",
|
|
585
|
-
"Error ending after missed call notif action",
|
|
586
|
-
e
|
|
587
|
-
)
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
},
|
|
591
|
-
incomingCall = {
|
|
592
|
-
if (this.savedContext != null && initializationTime != 0L) {
|
|
593
|
-
val contextCreatedAt = initializationTime
|
|
594
|
-
val now = System.currentTimeMillis()
|
|
595
|
-
val isWithinOneSecond = (now - contextCreatedAt) <= 1000L
|
|
596
|
-
|
|
597
|
-
Log.i(
|
|
598
|
-
"StreamCallPlugin",
|
|
599
|
-
"Time between context creation and activity created (incoming call notif): ${now - contextCreatedAt}"
|
|
600
|
-
)
|
|
601
|
-
if (isWithinOneSecond && !bootedToHandleCall) {
|
|
602
|
-
Log.i(
|
|
603
|
-
"StreamCallPlugin",
|
|
604
|
-
"Notification incomingCall received less than 1 second after the creation of streamVideoSDK. Booted FOR SURE in order to handle the notification"
|
|
605
|
-
)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
)
|
|
610
|
-
|
|
611
584
|
val notificationConfig = NotificationConfig(
|
|
612
585
|
pushDeviceGenerators = listOf(
|
|
613
586
|
FirebasePushDeviceGenerator(
|
|
@@ -616,7 +589,23 @@ class StreamCallPlugin : Plugin() {
|
|
|
616
589
|
)
|
|
617
590
|
),
|
|
618
591
|
requestPermissionOnAppLaunch = { true },
|
|
619
|
-
notificationHandler =
|
|
592
|
+
notificationHandler = CompatibilityStreamNotificationHandler(
|
|
593
|
+
application = application,
|
|
594
|
+
intentResolver = CustomStreamIntentResolver(application),
|
|
595
|
+
initialNotificationBuilderInterceptor = object : StreamNotificationBuilderInterceptors() {
|
|
596
|
+
override fun onBuildIncomingCallNotification(
|
|
597
|
+
builder: NotificationCompat.Builder,
|
|
598
|
+
fullScreenPendingIntent: PendingIntent,
|
|
599
|
+
acceptCallPendingIntent: PendingIntent,
|
|
600
|
+
rejectCallPendingIntent: PendingIntent,
|
|
601
|
+
callerName: String?,
|
|
602
|
+
shouldHaveContentIntent: Boolean
|
|
603
|
+
): NotificationCompat.Builder {
|
|
604
|
+
return builder.setContentIntent(fullScreenPendingIntent)
|
|
605
|
+
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
)
|
|
620
609
|
)
|
|
621
610
|
|
|
622
611
|
val soundsConfig = incomingOnlyRingingConfig()
|
|
@@ -1976,28 +1965,41 @@ class StreamCallPlugin : Plugin() {
|
|
|
1976
1965
|
}
|
|
1977
1966
|
|
|
1978
1967
|
@OptIn(DelicateCoroutinesApi::class)
|
|
1968
|
+
private suspend fun _endCall(): Boolean {
|
|
1969
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1970
|
+
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1971
|
+
|
|
1972
|
+
val callToEnd = activeCall ?: ringingCall
|
|
1973
|
+
|
|
1974
|
+
if (callToEnd == null) {
|
|
1975
|
+
Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
|
|
1976
|
+
return false
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
|
|
1980
|
+
|
|
1981
|
+
return try {
|
|
1982
|
+
endCallRaw(callToEnd)
|
|
1983
|
+
true
|
|
1984
|
+
} catch (e: Exception) {
|
|
1985
|
+
Log.e("StreamCallPlugin", "Error ending call: ${e.message}", e)
|
|
1986
|
+
false
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1979
1990
|
@PluginMethod
|
|
1980
1991
|
fun endCall(call: PluginCall) {
|
|
1981
1992
|
try {
|
|
1982
|
-
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1983
|
-
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1984
|
-
|
|
1985
|
-
val callToEnd = activeCall ?: ringingCall
|
|
1986
|
-
|
|
1987
|
-
if (callToEnd == null) {
|
|
1988
|
-
Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
|
|
1989
|
-
call.reject("No active call to end")
|
|
1990
|
-
return
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
|
|
1994
|
-
|
|
1995
1993
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1996
1994
|
try {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
1995
|
+
val success = _endCall()
|
|
1996
|
+
if (success) {
|
|
1997
|
+
call.resolve(JSObject().apply {
|
|
1998
|
+
put("success", true)
|
|
1999
|
+
})
|
|
2000
|
+
} else {
|
|
2001
|
+
call.reject("No active call to end")
|
|
2002
|
+
}
|
|
2001
2003
|
} catch (e: Exception) {
|
|
2002
2004
|
Log.e("StreamCallPlugin", "Error ending call: ${e.message}")
|
|
2003
2005
|
call.reject("Failed to end call: ${e.message}")
|
|
@@ -2501,6 +2503,35 @@ class StreamCallPlugin : Plugin() {
|
|
|
2501
2503
|
}
|
|
2502
2504
|
}
|
|
2503
2505
|
|
|
2506
|
+
@PluginMethod
|
|
2507
|
+
fun getCurrentUser(call: PluginCall) {
|
|
2508
|
+
Log.d("StreamCallPlugin", "getCurrentUser called")
|
|
2509
|
+
try {
|
|
2510
|
+
val savedCredentials = SecureUserRepository.getInstance(context).loadCurrentUser()
|
|
2511
|
+
val ret = JSObject()
|
|
2512
|
+
|
|
2513
|
+
if (savedCredentials != null) {
|
|
2514
|
+
Log.d("StreamCallPlugin", "getCurrentUser: Found saved credentials for user: ${savedCredentials.user.id}")
|
|
2515
|
+
ret.put("userId", savedCredentials.user.id)
|
|
2516
|
+
ret.put("name", savedCredentials.user.name ?: "")
|
|
2517
|
+
ret.put("imageURL", savedCredentials.user.image ?: "")
|
|
2518
|
+
ret.put("isLoggedIn", true)
|
|
2519
|
+
} else {
|
|
2520
|
+
Log.d("StreamCallPlugin", "getCurrentUser: No saved credentials found")
|
|
2521
|
+
ret.put("userId", "")
|
|
2522
|
+
ret.put("name", "")
|
|
2523
|
+
ret.put("imageURL", "")
|
|
2524
|
+
ret.put("isLoggedIn", false)
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
Log.d("StreamCallPlugin", "getCurrentUser: Returning ${ret}")
|
|
2528
|
+
call.resolve(ret)
|
|
2529
|
+
} catch (e: Exception) {
|
|
2530
|
+
Log.e("StreamCallPlugin", "getCurrentUser: Failed to get current user", e)
|
|
2531
|
+
call.reject("Failed to get current user", e)
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2504
2535
|
companion object {
|
|
2505
2536
|
@JvmStatic fun preLoadInit(ctx: Context, app: Application) {
|
|
2506
2537
|
holder ?: run {
|
|
@@ -50,6 +50,8 @@ class SecureUserRepository private constructor(context: Context) : UserRepositor
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
override fun save(user: UserCredentials) {
|
|
53
|
+
android.util.Log.d("SecureUserRepository", "Saving user credentials for: ${user.user.id}")
|
|
54
|
+
|
|
53
55
|
val customJson = user.user.custom?.let { customMap ->
|
|
54
56
|
JSONObject().apply {
|
|
55
57
|
customMap.forEach { (key, value) ->
|
|
@@ -70,6 +72,8 @@ class SecureUserRepository private constructor(context: Context) : UserRepositor
|
|
|
70
72
|
putString(KEY_USER, userJson.toString())
|
|
71
73
|
putString(KEY_TOKEN, user.tokenValue)
|
|
72
74
|
}
|
|
75
|
+
|
|
76
|
+
android.util.Log.d("SecureUserRepository", "User credentials saved successfully for: ${user.user.id}")
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
override fun save(token: String) {
|
|
@@ -77,6 +81,8 @@ class SecureUserRepository private constructor(context: Context) : UserRepositor
|
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
override fun loadCurrentUser(): UserCredentials? {
|
|
84
|
+
android.util.Log.d("SecureUserRepository", "Loading current user credentials")
|
|
85
|
+
|
|
80
86
|
val userJson = sharedPreferences.getString(KEY_USER, null)
|
|
81
87
|
val token = sharedPreferences.getString(KEY_TOKEN, null)
|
|
82
88
|
|
|
@@ -91,20 +97,26 @@ class SecureUserRepository private constructor(context: Context) : UserRepositor
|
|
|
91
97
|
role = jsonObject.optString("role"),
|
|
92
98
|
custom = ArrayMap()
|
|
93
99
|
)
|
|
94
|
-
UserCredentials(user, token)
|
|
100
|
+
val credentials = UserCredentials(user, token)
|
|
101
|
+
android.util.Log.d("SecureUserRepository", "Successfully loaded credentials for user: ${user.id}")
|
|
102
|
+
credentials
|
|
95
103
|
} else {
|
|
104
|
+
android.util.Log.d("SecureUserRepository", "No stored credentials found (userJson: ${userJson != null}, token: ${token != null})")
|
|
96
105
|
null
|
|
97
106
|
}
|
|
98
107
|
} catch (e: Exception) {
|
|
108
|
+
android.util.Log.e("SecureUserRepository", "Error loading user credentials", e)
|
|
99
109
|
e.printStackTrace()
|
|
100
110
|
null
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
override fun removeCurrentUser() {
|
|
115
|
+
android.util.Log.d("SecureUserRepository", "Removing current user credentials")
|
|
105
116
|
sharedPreferences.edit {
|
|
106
117
|
remove(KEY_USER)
|
|
107
118
|
remove(KEY_TOKEN)
|
|
108
119
|
}
|
|
120
|
+
android.util.Log.d("SecureUserRepository", "User credentials removed successfully")
|
|
109
121
|
}
|
|
110
|
-
}
|
|
122
|
+
}
|
package/dist/docs.json
CHANGED
|
@@ -483,6 +483,27 @@
|
|
|
483
483
|
"DynamicApiKeyResponse"
|
|
484
484
|
],
|
|
485
485
|
"slug": "getdynamicstreamvideoapikey"
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
"name": "getCurrentUser",
|
|
489
|
+
"signature": "() => Promise<CurrentUserResponse>",
|
|
490
|
+
"parameters": [],
|
|
491
|
+
"returns": "Promise<CurrentUserResponse>",
|
|
492
|
+
"tags": [
|
|
493
|
+
{
|
|
494
|
+
"name": "returns",
|
|
495
|
+
"text": "Current user information"
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
"name": "example",
|
|
499
|
+
"text": "const currentUser = await StreamCall.getCurrentUser();\nconsole.log(currentUser);"
|
|
500
|
+
}
|
|
501
|
+
],
|
|
502
|
+
"docs": "Get the current user's information",
|
|
503
|
+
"complexTypes": [
|
|
504
|
+
"CurrentUserResponse"
|
|
505
|
+
],
|
|
506
|
+
"slug": "getcurrentuser"
|
|
486
507
|
}
|
|
487
508
|
],
|
|
488
509
|
"properties": []
|
|
@@ -1462,6 +1483,68 @@
|
|
|
1462
1483
|
"type": "boolean"
|
|
1463
1484
|
}
|
|
1464
1485
|
]
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
"name": "CurrentUserResponse",
|
|
1489
|
+
"slug": "currentuserresponse",
|
|
1490
|
+
"docs": "",
|
|
1491
|
+
"tags": [
|
|
1492
|
+
{
|
|
1493
|
+
"text": "CurrentUserResponse",
|
|
1494
|
+
"name": "interface"
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
"text": "Response from getCurrentUser containing user information",
|
|
1498
|
+
"name": "description"
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
"text": "{string} userId - User ID of the current user",
|
|
1502
|
+
"name": "property"
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
"text": "{string} name - Display name of the current user",
|
|
1506
|
+
"name": "property"
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
"text": "{string} [imageURL] - Avatar URL of the current user",
|
|
1510
|
+
"name": "property"
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
"text": "{boolean} isLoggedIn - Whether the user is currently logged in",
|
|
1514
|
+
"name": "property"
|
|
1515
|
+
}
|
|
1516
|
+
],
|
|
1517
|
+
"methods": [],
|
|
1518
|
+
"properties": [
|
|
1519
|
+
{
|
|
1520
|
+
"name": "userId",
|
|
1521
|
+
"tags": [],
|
|
1522
|
+
"docs": "User ID of the current user",
|
|
1523
|
+
"complexTypes": [],
|
|
1524
|
+
"type": "string"
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
"name": "name",
|
|
1528
|
+
"tags": [],
|
|
1529
|
+
"docs": "Display name of the current user",
|
|
1530
|
+
"complexTypes": [],
|
|
1531
|
+
"type": "string"
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
"name": "imageURL",
|
|
1535
|
+
"tags": [],
|
|
1536
|
+
"docs": "Avatar URL of the current user",
|
|
1537
|
+
"complexTypes": [],
|
|
1538
|
+
"type": "string | undefined"
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
"name": "isLoggedIn",
|
|
1542
|
+
"tags": [],
|
|
1543
|
+
"docs": "Whether the user is currently logged in",
|
|
1544
|
+
"complexTypes": [],
|
|
1545
|
+
"type": "boolean"
|
|
1546
|
+
}
|
|
1547
|
+
]
|
|
1465
1548
|
}
|
|
1466
1549
|
],
|
|
1467
1550
|
"enums": [
|
|
@@ -98,6 +98,24 @@ export interface DynamicApiKeyResponse {
|
|
|
98
98
|
/** Whether a dynamic key is currently set */
|
|
99
99
|
hasDynamicKey: boolean;
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* @interface CurrentUserResponse
|
|
103
|
+
* @description Response from getCurrentUser containing user information
|
|
104
|
+
* @property {string} userId - User ID of the current user
|
|
105
|
+
* @property {string} name - Display name of the current user
|
|
106
|
+
* @property {string} [imageURL] - Avatar URL of the current user
|
|
107
|
+
* @property {boolean} isLoggedIn - Whether the user is currently logged in
|
|
108
|
+
*/
|
|
109
|
+
export interface CurrentUserResponse {
|
|
110
|
+
/** User ID of the current user */
|
|
111
|
+
userId: string;
|
|
112
|
+
/** Display name of the current user */
|
|
113
|
+
name: string;
|
|
114
|
+
/** Avatar URL of the current user */
|
|
115
|
+
imageURL?: string;
|
|
116
|
+
/** Whether the user is currently logged in */
|
|
117
|
+
isLoggedIn: boolean;
|
|
118
|
+
}
|
|
101
119
|
/**
|
|
102
120
|
* @interface SuccessResponse
|
|
103
121
|
* @description Standard response indicating operation success/failure
|
|
@@ -299,6 +317,14 @@ export interface StreamCallPlugin {
|
|
|
299
317
|
* }
|
|
300
318
|
*/
|
|
301
319
|
getDynamicStreamVideoApikey(): Promise<DynamicApiKeyResponse>;
|
|
320
|
+
/**
|
|
321
|
+
* Get the current user's information
|
|
322
|
+
* @returns {Promise<CurrentUserResponse>} Current user information
|
|
323
|
+
* @example
|
|
324
|
+
* const currentUser = await StreamCall.getCurrentUser();
|
|
325
|
+
* console.log(currentUser);
|
|
326
|
+
*/
|
|
327
|
+
getCurrentUser(): Promise<CurrentUserResponse>;
|
|
302
328
|
}
|
|
303
329
|
/**
|
|
304
330
|
* @interface IncomingCallPayload
|