Fix call screen crash when participant count drops during speaker view.

This commit is contained in:
Alex Hart
2026-05-12 10:47:28 -03:00
committed by GitHub
parent c4e7841ea3
commit fc448ecb59
4 changed files with 46 additions and 55 deletions
@@ -15,7 +15,6 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
@@ -52,67 +51,55 @@ fun CallParticipantsPager(
callParticipantsPagerState.callParticipants.firstOrNull()?.videoSink
)
// Use movableContentOf to preserve CallGrid state when switching between
// single participant (no pager) and multiple participants (with pager)
val callGridContent = remember {
movableContentOf { state: CallParticipantsPagerState, mod: Modifier, aspectRatio: Float? ->
CallGrid(
items = state.callParticipants,
singleParticipantAspectRatio = aspectRatio,
modifier = mod,
itemKey = { it.callParticipantId }
) { participant, itemModifier ->
val longPressModifier = if (!participant.recipient.isSelf && currentOnLongPress.value != null) {
var itemWindowOrigin by remember(participant.callParticipantId) { mutableStateOf(Offset.Zero) }
itemModifier
.onGloballyPositioned { coords -> itemWindowOrigin = coords.positionInRoot() }
.pointerInput(participant.callParticipantId) {
detectTapGestures(
onTap = { currentOnTap.value?.invoke() },
onLongPress = { local -> currentOnLongPress.value?.invoke(participant, itemWindowOrigin + local) }
)
}
} else {
itemModifier
}
VerticalPager(
state = pagerState,
modifier = modifier
.displayCutoutPadding()
.statusBarsPadding()
) { page ->
when (page) {
0 -> {
CallGrid(
items = callParticipantsPagerState.callParticipants,
singleParticipantAspectRatio = firstParticipantAR,
modifier = Modifier.fillMaxSize(),
itemKey = { it.callParticipantId }
) { participant, itemModifier ->
val longPressModifier = if (!participant.recipient.isSelf && currentOnLongPress.value != null) {
var itemWindowOrigin by remember(participant.callParticipantId) { mutableStateOf(Offset.Zero) }
itemModifier
.onGloballyPositioned { coords -> itemWindowOrigin = coords.positionInRoot() }
.pointerInput(participant.callParticipantId) {
detectTapGestures(
onTap = { currentOnTap.value?.invoke() },
onLongPress = { local -> currentOnLongPress.value?.invoke(participant, itemWindowOrigin + local) }
)
}
} else {
itemModifier
}
RemoteParticipantContent(
participant = participant,
renderInPip = state.isRenderInPip,
raiseHandAllowed = false,
onInfoMoreInfoClick = null,
showAudioIndicator = state.callParticipants.size > 1,
modifier = longPressModifier
)
}
}
}
if (callParticipantsPagerState.callParticipants.size > 1) {
VerticalPager(
state = pagerState,
modifier = modifier
.displayCutoutPadding()
.statusBarsPadding()
) { page ->
when (page) {
0 -> {
callGridContent(callParticipantsPagerState, Modifier.fillMaxSize(), firstParticipantAR)
}
1 -> {
RemoteParticipantContent(
participant = callParticipantsPagerState.focusedParticipant,
participant = participant,
renderInPip = callParticipantsPagerState.isRenderInPip,
raiseHandAllowed = false,
onInfoMoreInfoClick = null,
modifier = Modifier.fillMaxSize()
showAudioIndicator = callParticipantsPagerState.callParticipants.size > 1,
modifier = longPressModifier
)
}
}
1 -> {
RemoteParticipantContent(
participant = callParticipantsPagerState.focusedParticipant,
renderInPip = callParticipantsPagerState.isRenderInPip,
raiseHandAllowed = false,
onInfoMoreInfoClick = null,
modifier = Modifier.fillMaxSize()
)
}
}
} else {
callGridContent(callParticipantsPagerState, modifier, firstParticipantAR)
}
}
@@ -104,8 +104,10 @@ fun CallScreen(
savedLocalParticipantLandscape: Boolean = false,
callScreenState: CallScreenState,
callControlsState: CallControlsState,
callParticipantsPagerState: CallParticipantsPagerState,
callScreenController: CallScreenController = CallScreenController.rememberCallScreenController(
skipHiddenState = callControlsState.skipHiddenState,
hasMultipleRemoteParticipants = callParticipantsPagerState.callParticipants.size > 1,
onControlsToggled = {},
callControlsState = callControlsState,
callControlsListener = CallScreenControlsListener.Empty
@@ -113,7 +115,6 @@ fun CallScreen(
callScreenControlsListener: CallScreenControlsListener = CallScreenControlsListener.Empty,
callScreenSheetDisplayListener: CallScreenSheetDisplayListener = CallScreenSheetDisplayListener.Empty,
additionalActionsListener: AdditionalActionsListener = AdditionalActionsListener.Empty,
callParticipantsPagerState: CallParticipantsPagerState,
pendingParticipantsListener: PendingParticipantsListener = PendingParticipantsListener.Empty,
callParticipantUpdatePopupController: CallParticipantUpdatePopupController,
overflowParticipants: List<CallParticipant>,
@@ -77,11 +77,13 @@ class CallScreenController private constructor(
@Composable
fun rememberCallScreenController(
skipHiddenState: Boolean,
hasMultipleRemoteParticipants: Boolean,
onControlsToggled: (Boolean) -> Unit,
callControlsState: CallControlsState,
callControlsListener: CallScreenControlsListener
): CallScreenController {
val skip by rememberUpdatedState(skipHiddenState)
val hasMultipleRemoteParticipantsState = rememberUpdatedState(hasMultipleRemoteParticipants)
val valueChangeOperation: (SheetValue) -> Boolean = remember {
{
!(it == SheetValue.Hidden && skip)
@@ -130,7 +132,7 @@ class CallScreenController private constructor(
val callParticipantsVerticalPagerState = rememberPagerState(
initialPage = 0,
pageCount = { 2 }
pageCount = { if (hasMultipleRemoteParticipantsState.value) 2 else 1 }
)
return remember(scaffoldState, callParticipantsVerticalPagerState, audioOutputPickerController) {
@@ -164,6 +164,7 @@ class ComposeCallScreenMediator(private val activity: WebRtcCallActivity, viewMo
val callScreenController = CallScreenController.rememberCallScreenController(
skipHiddenState = callControlsState.skipHiddenState,
hasMultipleRemoteParticipants = callParticipantsPagerState.callParticipants.size > 1,
onControlsToggled = onControlsToggled,
callControlsState = callControlsState,
callControlsListener = callScreenControlsListener