From 581e34c690e2acdabc6a4ae446e7e94632015cdc Mon Sep 17 00:00:00 2001 From: Aleksandar Apostolov Date: Mon, 27 May 2024 14:14:55 +0200 Subject: [PATCH] Allow for comparator to be updated in the `SortedParticipantState` (#1097) --- .../api/stream-video-android-core.api | 1 + .../getstream/video/android/core/CallState.kt | 72 +++++-------------- .../core/sorting/SortedParticipantsState.kt | 7 ++ .../video/android/core/CallStateTest.kt | 29 ++++++++ 4 files changed, 53 insertions(+), 56 deletions(-) diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index ab4861b4d2..d8e250225f 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -174,6 +174,7 @@ public final class io/getstream/video/android/core/CallState { public final fun updateFromResponse (Lorg/openapitools/client/models/StopLiveResponse;)V public final fun updateFromResponse (Lorg/openapitools/client/models/UpdateCallResponse;)V public final fun updateParticipant (Lio/getstream/video/android/core/ParticipantState;)V + public final fun updateParticipantSortingOrder (Ljava/util/Comparator;)V public final fun updateParticipantVisibility (Ljava/lang/String;Lio/getstream/video/android/core/model/VisibilityOnScreenState;)V public final fun updateParticipantVisibilityFlow (Lkotlinx/coroutines/flow/Flow;)V public final fun upsertParticipants (Ljava/util/List;)V diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index 50f1537e7e..2e875eb0be 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -293,56 +293,12 @@ public class CallState( val livestream: StateFlow = livestreamFlow.debounce(1000) .stateIn(scope, SharingStarted.WhileSubscribed(10000L), null) - internal val sortedParticipantsFlow = channelFlow { - // uses a channel flow to handle concurrency and 3 things updating: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/channel-flow.html - - fun emitSorted() { - val participants = participants.value - val pinned = _pinnedParticipants.value - var lastParticipants: List? = null - val sorted = participants.sortedWith( - compareBy( - { pinned.containsKey(it.sessionId) }, - { it.screenSharingEnabled.value }, - { it.dominantSpeaker.value }, - { it.videoEnabled.value }, - { it.lastSpeakingAt.value }, - { it.joinedAt.value }, - { it.userId.value }, - ), - ).reversed() - scope.launch { - if (lastParticipants != sorted) { - send(sorted) - lastParticipants = sorted - } - } - } - - scope.launch { - _participants.collect { - emitSorted() - } - } - // Since participant state exposes it's own stateflows this is a little harder to do than usual - // we need to listen to the events and update the flow when it changes - - // emit the sorted list - emitSorted() - - // TODO: could optimize performance by subscribing only to relevant events - call.subscribe { - emitSorted() - } - - scope.launch { - _pinnedParticipants.collect { - emitSorted() - } - } - - awaitClose {} - } + private var _sortedParticipantsState = SortedParticipantsState( + scope, + call, + _participants, + _pinnedParticipants, + ) /** * Sorted participants based on @@ -355,12 +311,16 @@ public class CallState( * * Debounced 100ms to avoid rapid changes */ - val sortedParticipants = SortedParticipantsState( - scope, - call, - _participants, - _pinnedParticipants, - ).asFlow().debounce(100) + val sortedParticipants = _sortedParticipantsState.asFlow().debounce(100) + + /** + * Update participant sorting order + * + * @param comparator a new comparator to be used in [sortedParticipants] flow. + */ + fun updateParticipantSortingOrder( + comparator: Comparator, + ) = _sortedParticipantsState.updateComparator(comparator) /** Members contains the list of users who are permanently associated with this call. This includes users who are currently not active in the call * As an example if you invite "john", "bob" and "jane" to a call and only Jane joins. diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sorting/SortedParticipantsState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sorting/SortedParticipantsState.kt index ad50ff4089..17d81daf35 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sorting/SortedParticipantsState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sorting/SortedParticipantsState.kt @@ -136,4 +136,11 @@ internal class SortedParticipantsState( * Get last sorted participants. */ fun lastSortOrder() = lastSortOrder + + /** + * Update the sorter with a new comparator. + */ + fun updateComparator(comparator: Comparator) { + this.comparator = comparator + } } diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallStateTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallStateTest.kt index 72d9dca557..82104aaeb7 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallStateTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallStateTest.kt @@ -170,6 +170,35 @@ class CallStateTest : IntegrationTestBase() { assertThat(sorted3).isEqualTo(listOf("1", "2", "3", "4", "5", "6")) } + @Test + fun `Update sorting order`() = runTest { + val call = client.call("default", randomUUID()) + val sortedParticipants = call.state.sortedParticipants.stateIn( + this, + SharingStarted.Eagerly, + emptyList(), + ) + call.state.updateParticipantSortingOrder( + compareByDescending { + it.sessionId + }, + ) + + call.state.updateParticipant( + ParticipantState("1", call, "1"), + ) + call.state.updateParticipant( + ParticipantState("2", call, "2").apply { _screenSharingEnabled.value = true }, + ) + call.state.updateParticipant( + ParticipantState("3", call, "3").apply { _dominantSpeaker.value = true }, + ) + + delay(1000) + val sorted = sortedParticipants.value.map { it.sessionId } + assertThat(sorted).isEqualTo(listOf("3", "2", "1")) + } + @Test fun `Querying calls should populate the state`() = runTest { // val createResult = client.call("default", randomUUID()).create(custom=mapOf("color" to "green"))