Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8615d30
feat: add methods to update, delete, and manage custom items in calls
OnestarLee Sep 25, 2025
d31e031
chore: rename `updateCustomItemsInPool` to `updateCustomItems` for co…
OnestarLee Sep 29, 2025
d1a07a9
Update ios/Modules/CallsModule+GroupCall.swift
OnestarLee Sep 29, 2025
827f91a
chore: refactor custom items handling and streamline `ReadableArray` …
OnestarLee Sep 29, 2025
78bcc04
Merge remote-tracking branch 'origin/feat/implement-custom-item' into…
OnestarLee Sep 29, 2025
7a1eaa5
feat: updated WebRTC version to M134 to support Android 16 KB page sizes
OnestarLee Sep 29, 2025
7cd525d
Update ios/Modules/CallsModule+GroupCall.swift
OnestarLee Sep 29, 2025
7910614
Update android/src/main/java/com/sendbird/calls/reactnative/module/Ca…
OnestarLee Sep 29, 2025
2647ffb
Update android/src/main/java/com/sendbird/calls/reactnative/module/Ca…
OnestarLee Sep 30, 2025
0d2bd03
Update android/src/main/java/com/sendbird/calls/reactnative/module/Ca…
OnestarLee Sep 30, 2025
71e78c2
Update android/src/main/java/com/sendbird/calls/reactnative/module/Ca…
OnestarLee Sep 30, 2025
af32752
Update android/src/main/java/com/sendbird/calls/reactnative/module/Ca…
OnestarLee Sep 30, 2025
962f389
Update android/src/main/java/com/sendbird/calls/reactnative/module/Ca…
OnestarLee Sep 30, 2025
2476787
Update android/src/main/java/com/sendbird/calls/reactnative/module/Ca…
OnestarLee Sep 30, 2025
0464d02
chore: Refactor custom item deletion in CallsGroupCallModule and upda…
OnestarLee Sep 30, 2025
d4e4fee
chore: Update push token registration and unregistration to use FCM_V…
OnestarLee Sep 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dependencies {
// noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:${ReactNative.ext.getVersion("android", "kotlin")}"
implementation("com.sendbird.sdk:sendbird-calls:1.12.1")
implementation("com.sendbird.sdk:sendbird-calls:1.12.3")
}

ReactNative.shared.applyPackageVersion()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.sendbird.calls.reactnative.module

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableNativeMap
import com.sendbird.calls.*
import com.sendbird.calls.internal.PushTokenType
import com.sendbird.calls.reactnative.RNCallsInternalError
import com.sendbird.calls.reactnative.extension.rejectCalls
import com.sendbird.calls.reactnative.module.listener.CallsDirectCallListener
Expand Down Expand Up @@ -104,7 +107,7 @@ class CallsCommonModule(private val root: CallsModule): CommonModule {

override fun registerPushToken(token: String, unique: Boolean, promise: Promise) {
RNCallsLogger.d("[CommonModule] registerPushToken()")
SendBirdCall.registerPushToken(token, unique) { error ->
SendBirdCall.registerPushToken(token, PushTokenType.FCM_VOIP, unique) { error ->
error
?.let {
promise.rejectCalls(it)
Expand All @@ -117,7 +120,7 @@ class CallsCommonModule(private val root: CallsModule): CommonModule {

override fun unregisterPushToken(token: String, promise: Promise) {
RNCallsLogger.d("[CommonModule] unregisterPushToken()")
SendBirdCall.unregisterPushToken(token) { error ->
SendBirdCall.unregisterPushToken(token, PushTokenType.FCM_VOIP) { error ->
error
?.let {
promise.rejectCalls(it)
Expand Down Expand Up @@ -219,4 +222,50 @@ class CallsCommonModule(private val root: CallsModule): CommonModule {
promise.resolve(null)
}
}

override fun updateCustomItems(callId: String, customItems: ReadableMap, promise: Promise) {
RNCallsLogger.d("[CommonModule] updateCustomItems($callId)")
val items = CallsUtils.convertMapToHashMap(customItems)

SendBirdCall.updateCustomItems(callId, items) { updatedItems, affectedKeys, error ->
error?.let {
promise.rejectCalls(it)
} ?: run {
val result = WritableNativeMap()
result.putMap("updatedItems", CallsUtils.convertToJsMap(updatedItems as? Map<*, *> ?: emptyMap<String, String>()))
result.putArray("affectedKeys", CallsUtils.convertToJsArray(affectedKeys as? List<*> ?: emptyList<String>()))
promise.resolve(result)
}
}
}

override fun deleteCustomItems(callId: String, customItemKeys: ReadableArray, promise: Promise) {
RNCallsLogger.d("[CommonModule] deleteCustomItems($callId)")
val keys = CallsUtils.convertArrayToSet(customItemKeys)

SendBirdCall.deleteCustomItems(callId, keys) { updatedItems, affectedKeys, error ->
error?.let {
promise.rejectCalls(it)
} ?: run {
val result = WritableNativeMap()
result.putMap("updatedItems", CallsUtils.convertToJsMap(updatedItems as? Map<*, *> ?: emptyMap<String, String>()))
result.putArray("affectedKeys", CallsUtils.convertToJsArray(affectedKeys as? List<*> ?: emptyList<String>()))
promise.resolve(result)
}
}
}

override fun deleteAllCustomItems(callId: String, promise: Promise) {
RNCallsLogger.d("[CommonModule] deleteAllCustomItems($callId)")
SendBirdCall.deleteAllCustomItems(callId) { updatedItems, affectedKeys, error ->
error?.let {
promise.rejectCalls(it)
} ?: run {
val result = WritableNativeMap()
result.putMap("updatedItems", CallsUtils.convertToJsMap(updatedItems as? Map<*, *> ?: emptyMap<String, String>()))
result.putArray("affectedKeys", CallsUtils.convertToJsArray(affectedKeys as? List<*> ?: emptyList<String>()))
promise.resolve(result)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sendbird.calls.reactnative.module

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.sendbird.calls.AcceptParams
import com.sendbird.calls.AudioDevice
Expand Down Expand Up @@ -83,7 +84,7 @@ class CallsDirectCallModule(private val root: CallsModule): DirectCallModule {
call.setRemoteVideoView(view.getSurface())
}
}

override fun muteMicrophone(type: String, identifier: String) {
val from = "directCall/muteMicrophone"
RNCallsLogger.d("[DirectCallModule] $from ($identifier)")
Expand Down Expand Up @@ -200,4 +201,69 @@ class CallsDirectCallModule(private val root: CallsModule): DirectCallModule {
CallsUtils.findDirectCall(identifier, from).resumeAudioTrack()
}
}

override fun directCallUpdateCustomItems(callId: String, customItems: ReadableMap, promise: Promise) {
val from = "directCall/updateCustomItems"
RNCallsLogger.d("[DirectCallModule] $from ($callId)")

CallsUtils.safeRun(promise) {
val call = CallsUtils.findDirectCall(callId, from)
val items = CallsUtils.convertMapToHashMap(customItems)

call.updateCustomItems(items) { updatedItems, affectedKeys, error ->
if (error != null) {
promise.rejectCalls(error)
} else {
val result = CallsUtils.createMap().apply {
putMap("updatedItems", CallsUtils.convertHashMapToMap(updatedItems ?: hashMapOf()))
putArray("affectedKeys", CallsUtils.convertListToArray(affectedKeys ?: listOf()))
}
promise.resolve(result)
}
}
}
}

override fun directCallDeleteCustomItems(callId: String, customItemKeys: ReadableArray, promise: Promise) {
val from = "directCall/deleteCustomItems"
RNCallsLogger.d("[DirectCallModule] $from ($callId)")

CallsUtils.safeRun(promise) {
val call = CallsUtils.findDirectCall(callId, from)
val keys = CallsUtils.convertArrayToSet(customItemKeys)

call.deleteCustomItems(keys) { updatedItems, affectedKeys, error ->
if (error != null) {
promise.rejectCalls(error)
} else {
val result = CallsUtils.createMap().apply {
putMap("updatedItems", CallsUtils.convertHashMapToMap(updatedItems ?: hashMapOf()))
putArray("affectedKeys", CallsUtils.convertListToArray(affectedKeys ?: listOf()))
}
promise.resolve(result)
}
}
}
}

override fun directCallDeleteAllCustomItems(callId: String, promise: Promise) {
val from = "directCall/deleteAllCustomItems"
RNCallsLogger.d("[DirectCallModule] $from ($callId)")

CallsUtils.safeRun(promise) {
val call = CallsUtils.findDirectCall(callId, from)

call.deleteAllCustomItems { updatedItems, affectedKeys, error ->
if (error != null) {
promise.rejectCalls(error)
} else {
val result = CallsUtils.createMap().apply {
putMap("updatedItems", CallsUtils.convertHashMapToMap(updatedItems ?: hashMapOf()))
putArray("affectedKeys", CallsUtils.convertListToArray(affectedKeys ?: listOf()))
}
promise.resolve(result)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sendbird.calls.reactnative.module

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.sendbird.calls.*
import com.sendbird.calls.reactnative.RNCallsInternalError
Expand Down Expand Up @@ -147,4 +148,69 @@ class CallsGroupCallModule: GroupCallModule {
CallsUtils.findRoom(identifier, from).localParticipant?.resumeAudioTrack()
}
}

override fun groupCallUpdateCustomItems(roomId: String, customItems: ReadableMap, promise: Promise) {
val from = "groupCall/updateCustomItems"
RNCallsLogger.d("[GroupCallModule] $from ($roomId)")

CallsUtils.safeRun(promise) {
val room = CallsUtils.findRoom(roomId, from)
val items = CallsUtils.convertMapToHashMap(customItems)

room.updateCustomItems(items) { updatedItems, affectedKeys, error ->
if (error != null) {
promise.rejectCalls(error)
} else {
val result = CallsUtils.createMap().apply {
putMap("updatedItems", CallsUtils.convertHashMapToMap(updatedItems ?: hashMapOf()))
putArray("affectedKeys", CallsUtils.convertListToArray(affectedKeys ?: listOf()))
}
promise.resolve(result)
}
}
}
}

override fun groupCallDeleteCustomItems(roomId: String, customItemKeys: ReadableArray, promise: Promise) {
val from = "groupCall/deleteCustomItems"
RNCallsLogger.d("[GroupCallModule] $from ($roomId)")

CallsUtils.safeRun(promise) {
val room = CallsUtils.findRoom(roomId, from)
val keys = CallsUtils.convertArrayToSet(customItemKeys)

room.deleteCustomItems(keys) { updatedItems, affectedKeys, error ->
if (error != null) {
promise.rejectCalls(error)
} else {
val result = CallsUtils.createMap().apply {
putMap("updatedItems", CallsUtils.convertHashMapToMap(updatedItems ?: hashMapOf()))
putArray("affectedKeys", CallsUtils.convertListToArray(affectedKeys ?: listOf()))
}
promise.resolve(result)
}
}
}
}

override fun groupCallDeleteAllCustomItems(roomId: String, promise: Promise) {
val from = "groupCall/deleteAllCustomItems"
RNCallsLogger.d("[GroupCallModule] $from ($roomId)")

CallsUtils.safeRun(promise) {
val room = CallsUtils.findRoom(roomId, from)
// There is no deleteAllCustomItems API in Android native, so handled with deleteCustomItems.
room.deleteCustomItems(null) { updatedItems, affectedKeys, error ->
if (error != null) {
promise.rejectCalls(error)
} else {
val result = CallsUtils.createMap().apply {
putMap("updatedItems", CallsUtils.convertHashMapToMap(updatedItems ?: emptyMap()))
putArray("affectedKeys", CallsUtils.convertListToArray(affectedKeys ?: listOf()))
}
promise.resolve(result)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.sendbird.calls.reactnative.module

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.sendbird.calls.DirectCall
import com.sendbird.calls.RoomInvitation
Expand Down Expand Up @@ -76,6 +77,9 @@ class CallsModule(val reactContext: ReactApplicationContext) : CallsModuleStruct
override fun createRoom(params: ReadableMap, promise: Promise) = commonModule.createRoom(params, promise)
override fun fetchRoomById(roomId: String, promise: Promise) = commonModule.fetchRoomById(roomId, promise)
override fun getCachedRoomById(roomId: String, promise: Promise) = commonModule.getCachedRoomById(roomId, promise)
override fun updateCustomItems(callId: String, customItems: ReadableMap, promise: Promise) = commonModule.updateCustomItems(callId, customItems, promise)
override fun deleteCustomItems(callId: String, customItemKeys: ReadableArray, promise: Promise) = commonModule.deleteCustomItems(callId, customItemKeys, promise)
override fun deleteAllCustomItems(callId: String, promise: Promise) = commonModule.deleteAllCustomItems(callId, promise)

/** Media Device control interface **/
override fun stopVideo(type: String, identifier: String) = getControllableModule(type).stopVideo(type, identifier)
Expand All @@ -93,10 +97,16 @@ class CallsModule(val reactContext: ReactApplicationContext) : CallsModuleStruct
override fun end(callId: String, promise: Promise)= directCallModule.end(callId, promise)
override fun updateLocalVideoView(callId: String, videoViewId: Int)= directCallModule.updateLocalVideoView(callId, videoViewId)
override fun updateRemoteVideoView(callId: String, videoViewId: Int)= directCallModule.updateRemoteVideoView(callId, videoViewId)
override fun directCallUpdateCustomItems(callId: String, customItems: ReadableMap, promise: Promise) = directCallModule.directCallUpdateCustomItems(callId, customItems, promise)
override fun directCallDeleteCustomItems(callId: String, customItemKeys: ReadableArray, promise: Promise) = directCallModule.directCallDeleteCustomItems(callId, customItemKeys, promise)
override fun directCallDeleteAllCustomItems(callId: String, promise: Promise) = directCallModule.directCallDeleteAllCustomItems(callId, promise)

/** GroupCall module interface**/
override fun enter(roomId: String, options: ReadableMap, promise: Promise) = groupCallModule.enter(roomId, options, promise)
override fun exit(roomId: String) = groupCallModule.exit(roomId)
override fun groupCallUpdateCustomItems(roomId: String, customItems: ReadableMap, promise: Promise) = groupCallModule.groupCallUpdateCustomItems(roomId, customItems, promise)
override fun groupCallDeleteCustomItems(roomId: String, customItemKeys: ReadableArray, promise: Promise) = groupCallModule.groupCallDeleteCustomItems(roomId, customItemKeys, promise)
override fun groupCallDeleteAllCustomItems(roomId: String, promise: Promise) = groupCallModule.groupCallDeleteAllCustomItems(roomId, promise)

/** Queries **/
fun createDirectCallLogListQuery(params: ReadableMap, promise: Promise) = queries.createDirectCallLogListQuery(params, promise)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sendbird.calls.reactnative.module

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap

interface CallsModuleStruct: CommonModule, DirectCallModule, GroupCallModule { }
Expand Down Expand Up @@ -29,18 +30,28 @@ interface CommonModule {
fun createRoom(params: ReadableMap, promise: Promise)
fun fetchRoomById(roomId: String, promise: Promise)
fun getCachedRoomById(roomId: String, promise: Promise)

fun updateCustomItems(callId: String, customItems: ReadableMap, promise: Promise)
fun deleteCustomItems(callId: String, customItemKeys: ReadableArray, promise: Promise)
fun deleteAllCustomItems(callId: String, promise: Promise)
}

interface DirectCallModule: MediaDeviceControl {
fun accept(callId: String, options: ReadableMap, holdActiveCall: Boolean, promise: Promise)
fun end(callId: String, promise: Promise)
fun updateLocalVideoView(callId: String, videoViewId: Int)
fun updateRemoteVideoView(callId: String, videoViewId: Int)
fun directCallUpdateCustomItems(callId: String, customItems: ReadableMap, promise: Promise)
fun directCallDeleteCustomItems(callId: String, customItemKeys: ReadableArray, promise: Promise)
fun directCallDeleteAllCustomItems(callId: String, promise: Promise)
}

interface GroupCallModule: MediaDeviceControl {
fun enter(roomId: String, options: ReadableMap, promise: Promise)
fun exit(roomId: String)
fun groupCallUpdateCustomItems(roomId: String, customItems: ReadableMap, promise: Promise)
fun groupCallDeleteCustomItems(roomId: String, customItemKeys: ReadableArray, promise: Promise)
fun groupCallDeleteAllCustomItems(roomId: String, promise: Promise)
}

enum class ControllableModuleType {
Expand All @@ -58,4 +69,4 @@ interface MediaDeviceControl {
fun selectVideoDevice(type: String, identifier: String, device: ReadableMap, promise: Promise)
fun resumeVideoCapturer(type: String, identifier: String)
fun resumeAudioTrack(type: String, identifier: String)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,50 @@ object CallsUtils {
return SendBirdCall.getCachedRoomById(roomId) ?: throw RNCallsInternalError(from, RNCallsInternalError.Type.NOT_FOUND_ROOM)
}

fun createMap(): WritableNativeMap {
return WritableNativeMap()
}

fun convertMapToHashMap(map: ReadableMap): HashMap<String, String> {
val hashMap = HashMap<String, String>()
val iterator = map.keySetIterator()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
hashMap[key] = map.getString(key) ?: ""
}
return hashMap
}

fun convertHashMapToMap(hashMap: Map<String, String>): WritableNativeMap {
val map = WritableNativeMap()
for ((key, value) in hashMap) {
map.putString(key, value)
}
return map
}

fun convertArrayToSet(array: ReadableArray): Set<String> {
val set = mutableSetOf<String>()
for (i in 0 until array.size()) {
set.add(array.getString(i))
}
return set
}

fun convertArrayToList(array: ReadableArray): List<String> {
val list = mutableListOf<String>()
for (i in 0 until array.size()) {
list.add(array.getString(i))
}
return list
}

fun convertListToArray(list: List<String>): WritableNativeArray {
val array = WritableNativeArray()
for (item in list) {
array.pushString(item)
}
return array
}

}
Loading