simplify participant data structure

move ParticipantUiState into ParticipantDisplayItem

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2025-05-12 15:01:26 +02:00
parent 962972dce4
commit eaed93087b
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
7 changed files with 49 additions and 87 deletions

View File

@ -48,12 +48,12 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateListOf
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.graphics.toColorInt
import androidx.core.net.toUri
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -73,7 +73,6 @@ import com.nextcloud.talk.call.MessageSender
import com.nextcloud.talk.call.MessageSenderMcu
import com.nextcloud.talk.call.MessageSenderNoMcu
import com.nextcloud.talk.call.MutableLocalCallParticipantModel
import com.nextcloud.talk.call.ParticipantUiState
import com.nextcloud.talk.call.ReactionAnimator
import com.nextcloud.talk.call.components.ParticipantGrid
import com.nextcloud.talk.chat.ChatActivity
@ -154,7 +153,6 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import okhttp3.Cache
import org.apache.commons.lang3.StringEscapeUtils
import org.greenrobot.eventbus.Subscribe
@ -308,8 +306,6 @@ class CallActivity : CallBaseActivity() {
private var mediaPlayer: MediaPlayer? = null
private val participantItems = mutableStateListOf<ParticipantDisplayItem>()
private val participantUiStates = mutableStateListOf<ParticipantUiState>()
private var binding: CallActivityBinding? = null
private var audioOutputDialog: AudioOutputDialog? = null
private var moreCallActionsDialog: MoreCallActionsDialog? = null
@ -966,8 +962,9 @@ class CallActivity : CallBaseActivity() {
binding!!.composeParticipantGrid.setContent {
MaterialTheme {
val participantUiStates = participantItems.map { it.uiStateFlow.collectAsState().value }
ParticipantGrid(
participants = participantUiStates.toList(),
participantUiStates = participantUiStates,
eglBase = rootEglBase!!,
isVoiceOnlyCall = isVoiceOnlyCall
) {
@ -2555,7 +2552,6 @@ class CallActivity : CallBaseActivity() {
val participant = participantItems.find { it.sessionKey == key }
participant?.destroy()
participantItems.removeAll { it.sessionKey == key }
participantUiStates.removeAll { it.sessionKey == key }
initGrid()
}
@ -2674,17 +2670,6 @@ class CallActivity : CallBaseActivity() {
if (participantItems.none { it.sessionKey == sessionKey }) {
participantItems.add(participantDisplayItem)
lifecycleScope.launch {
participantDisplayItem.uiStateFlow.collect { uiState ->
val index = participantUiStates.indexOfFirst { it.sessionKey == sessionKey }
if (index >= 0) {
participantUiStates[index] = uiState
} else {
participantUiStates.add(uiState)
}
}
}
}
initGrid()

View File

@ -15,7 +15,6 @@ import android.text.TextUtils
import android.util.Log
import android.view.ViewGroup
import com.nextcloud.talk.call.CallParticipantModel
import com.nextcloud.talk.call.ParticipantUiState
import com.nextcloud.talk.call.RaisedHand
import com.nextcloud.talk.models.json.participants.Participant.ActorType
import com.nextcloud.talk.utils.ApiUtils.getUrlForAvatar
@ -30,6 +29,17 @@ import org.webrtc.MediaStream
import org.webrtc.PeerConnection.IceConnectionState
import org.webrtc.SurfaceViewRenderer
data class ParticipantUiState(
val sessionKey: String,
val nick: String,
val isConnected: Boolean,
val isAudioEnabled: Boolean,
val isStreamEnabled: Boolean,
val raisedHand: Boolean,
val avatarUrl: String?,
val mediaStream: MediaStream?
)
@Suppress("LongParameterList")
class ParticipantDisplayItem(
private val context: Context,

View File

@ -1,21 +0,0 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import org.webrtc.MediaStream
data class ParticipantUiState(
val sessionKey: String,
val nick: String,
val isConnected: Boolean,
val isAudioEnabled: Boolean,
val isStreamEnabled: Boolean,
val raisedHand: Boolean,
val avatarUrl: String?,
val mediaStream: MediaStream?
)

View File

@ -22,7 +22,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.nextcloud.talk.call.ParticipantUiState
import com.nextcloud.talk.adapters.ParticipantUiState
@Composable
fun AvatarWithFallback(participant: ParticipantUiState, modifier: Modifier = Modifier) {

View File

@ -5,6 +5,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@file:Suppress("MagicNumber", "TooManyFunctions")
package com.nextcloud.talk.call.components
import android.content.res.Configuration
@ -23,16 +25,15 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nextcloud.talk.call.ParticipantUiState
import com.nextcloud.talk.adapters.ParticipantUiState
import org.webrtc.EglBase
import kotlin.math.ceil
@Suppress("MagicNumber", "TooManyFunctions")
@Composable
fun ParticipantGrid(
modifier: Modifier = Modifier,
eglBase: EglBase?,
participants: List<ParticipantUiState>,
participantUiStates: List<ParticipantUiState>,
isVoiceOnlyCall: Boolean,
onClick: () -> Unit
) {
@ -43,19 +44,19 @@ fun ParticipantGrid(
val columns =
if (isPortrait) {
when (participants.size) {
when (participantUiStates.size) {
1, 2, 3 -> 1
else -> 2
}
} else {
when (participants.size) {
when (participantUiStates.size) {
1 -> 1
2, 4 -> 2
else -> 3
}
}
val rows = ceil(participants.size / columns.toFloat()).toInt()
val rows = ceil(participantUiStates.size / columns.toFloat()).toInt()
val heightForNonGridComponents = if (isVoiceOnlyCall) {
// this is a workaround for now. It should ~summarize the height of callInfosLinearLayout and callControls
@ -87,11 +88,11 @@ fun ParticipantGrid(
contentPadding = PaddingValues(vertical = edgePadding)
) {
items(
participants,
participantUiStates,
key = { it.sessionKey }
) { participant ->
ParticipantTile(
participant = participant,
participantUiState = participant,
modifier = Modifier
.height(itemHeight)
.fillMaxWidth(),
@ -102,84 +103,76 @@ fun ParticipantGrid(
}
}
@Suppress("MagicNumber")
@Preview
@Composable
fun ParticipantGridPreview() {
ParticipantGrid(
participants = getTestParticipants(1),
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview
@Composable
fun TwoParticipants() {
ParticipantGrid(
participants = getTestParticipants(2),
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview
@Composable
fun ThreeParticipants() {
ParticipantGrid(
participants = getTestParticipants(3),
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview
@Composable
fun FourParticipants() {
ParticipantGrid(
participants = getTestParticipants(4),
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview
@Composable
fun FiveParticipants() {
ParticipantGrid(
participants = getTestParticipants(5),
participantUiStates = getTestParticipants(5),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview
@Composable
fun SevenParticipants() {
ParticipantGrid(
participants = getTestParticipants(7),
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview
@Composable
fun FiftyParticipants() {
ParticipantGrid(
participants = getTestParticipants(50),
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview(
showBackground = false,
heightDp = 360,
@ -188,13 +181,12 @@ fun FiftyParticipants() {
@Composable
fun OneParticipantLandscape() {
ParticipantGrid(
participants = getTestParticipants(1),
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview(
showBackground = false,
heightDp = 360,
@ -203,13 +195,12 @@ fun OneParticipantLandscape() {
@Composable
fun TwoParticipantsLandscape() {
ParticipantGrid(
participants = getTestParticipants(2),
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview(
showBackground = false,
heightDp = 360,
@ -218,13 +209,12 @@ fun TwoParticipantsLandscape() {
@Composable
fun ThreeParticipantsLandscape() {
ParticipantGrid(
participants = getTestParticipants(3),
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview(
showBackground = false,
heightDp = 360,
@ -233,13 +223,12 @@ fun ThreeParticipantsLandscape() {
@Composable
fun FourParticipantsLandscape() {
ParticipantGrid(
participants = getTestParticipants(4),
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview(
showBackground = false,
heightDp = 360,
@ -248,13 +237,12 @@ fun FourParticipantsLandscape() {
@Composable
fun SevenParticipantsLandscape() {
ParticipantGrid(
participants = getTestParticipants(7),
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false
) {}
}
@Suppress("MagicNumber")
@Preview(
showBackground = false,
heightDp = 360,
@ -263,7 +251,7 @@ fun SevenParticipantsLandscape() {
@Composable
fun FiftyParticipantsLandscape() {
ParticipantGrid(
participants = getTestParticipants(50),
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false
) {}

View File

@ -29,7 +29,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nextcloud.talk.R
import com.nextcloud.talk.call.ParticipantUiState
import com.nextcloud.talk.adapters.ParticipantUiState
import com.nextcloud.talk.utils.ColorGenerator
import org.webrtc.EglBase
@ -38,28 +38,28 @@ const val NICK_BLUR_RADIUS = 4f
@Composable
fun ParticipantTile(
participant: ParticipantUiState,
participantUiState: ParticipantUiState,
eglBase: EglBase?,
modifier: Modifier = Modifier,
isVoiceOnlyCall: Boolean
) {
val colorInt = ColorGenerator.shared.usernameToColor(participant.nick)
val colorInt = ColorGenerator.shared.usernameToColor(participantUiState.nick)
Box(
modifier = modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(colorInt))
) {
if (!isVoiceOnlyCall && participant.isStreamEnabled && participant.mediaStream != null) {
WebRTCVideoView(participant, eglBase)
if (!isVoiceOnlyCall && participantUiState.isStreamEnabled && participantUiState.mediaStream != null) {
WebRTCVideoView(participantUiState, eglBase)
} else {
AvatarWithFallback(
participant = participant,
participant = participantUiState,
modifier = Modifier.align(Alignment.Center)
)
}
if (participant.raisedHand) {
if (participantUiState.raisedHand) {
Icon(
painter = painterResource(id = R.drawable.ic_hand_back_left),
contentDescription = "Raised Hand",
@ -71,7 +71,7 @@ fun ParticipantTile(
)
}
if (!participant.isAudioEnabled) {
if (!participantUiState.isAudioEnabled) {
Icon(
painter = painterResource(id = R.drawable.ic_mic_off_white_24px),
contentDescription = "Mic Off",
@ -84,7 +84,7 @@ fun ParticipantTile(
}
Text(
text = participant.nick,
text = participantUiState.nick,
color = Color.White,
modifier = Modifier
.align(Alignment.BottomStart)
@ -98,7 +98,7 @@ fun ParticipantTile(
)
)
if (!participant.isConnected) {
if (!participantUiState.isConnected) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
@ -120,7 +120,7 @@ fun ParticipantTilePreview() {
mediaStream = null
)
ParticipantTile(
participant = participant,
participantUiState = participant,
modifier = Modifier
.fillMaxWidth()
.height(300.dp),

View File

@ -11,7 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.nextcloud.talk.call.ParticipantUiState
import com.nextcloud.talk.adapters.ParticipantUiState
import org.webrtc.EglBase
import org.webrtc.SurfaceViewRenderer