Skip to content

Commit

Permalink
feat: show connecting indicator if user has bad/lost connection durin…
Browse files Browse the repository at this point in the history
…g a call (WPB-1125) (#2101)
  • Loading branch information
ohassine committed Aug 25, 2023
1 parent cc9dc2c commit 98a9f25
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 50 deletions.
Expand Up @@ -39,6 +39,7 @@ class UICallParticipantMapper @Inject constructor(
isCameraOn = participant.isCameraOn,
isSharingScreen = participant.isSharingScreen,
avatar = participant.avatarAssetId?.let { ImageAsset.UserAvatarAsset(wireSessionImageLoader, it) },
membership = userTypeMapper.toMembership(participant.userType)
membership = userTypeMapper.toMembership(participant.userType),
hasEstablishedAudio = participant.hasEstablishedAudio
)
}
Expand Up @@ -34,4 +34,5 @@ data class UICallParticipant(
val isSharingScreen: Boolean,
val avatar: ImageAsset.UserAvatarAsset? = null,
val membership: Membership,
val hasEstablishedAudio: Boolean
)
Expand Up @@ -30,12 +30,14 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material.ContentAlpha
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
Expand All @@ -44,6 +46,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
Expand Down Expand Up @@ -90,10 +93,11 @@ fun ParticipantTile(
onSelfUserVideoPreviewCreated: (view: View) -> Unit,
onClearSelfUserVideoPreview: () -> Unit
) {
val alpha = if (participantTitleState.hasEstablishedAudio) ContentAlpha.high else ContentAlpha.medium
Surface(
modifier = modifier,
color = colorsScheme().callingParticipantTileBackgroundColor,
shape = RoundedCornerShape(dimensions().corner6x)
shape = RoundedCornerShape(dimensions().corner6x),
) {
var size by remember { mutableStateOf(IntSize.Zero) }
var zoom by remember { mutableStateOf(1f) }
Expand All @@ -106,6 +110,7 @@ fun ParticipantTile(
AvatarTile(
modifier = Modifier
.fillMaxSize()
.alpha(alpha)
.constrainAs(avatar) { },
avatar = UserAvatarData(participantTitleState.avatar),
avatarSize = avatarSize
Expand Down Expand Up @@ -178,7 +183,8 @@ fun ParticipantTile(
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
},
isMuted = participantTitleState.isMuted
isMuted = participantTitleState.isMuted,
hasEstablishedAudio = participantTitleState.hasEstablishedAudio
)

UsernameTile(
Expand All @@ -191,7 +197,8 @@ fun ParticipantTile(
}
.widthIn(max = onGoingCallTileUsernameMaxWidth),
name = participantTitleState.name,
isSpeaking = participantTitleState.isSpeaking
isSpeaking = participantTitleState.isSpeaking,
hasEstablishedAudio = participantTitleState.hasEstablishedAudio
)
}
TileBorder(participantTitleState.isSpeaking)
Expand Down Expand Up @@ -277,32 +284,74 @@ private fun AvatarTile(
private fun UsernameTile(
modifier: Modifier,
name: String,
isSpeaking: Boolean
isSpeaking: Boolean,
hasEstablishedAudio: Boolean
) {
val color = if (isSpeaking) MaterialTheme.wireColorScheme.primary else Color.Black
val nameLabelColor = if (hasEstablishedAudio) Color.White else colorsScheme().secondaryText

Surface(
modifier = modifier,
shape = RoundedCornerShape(dimensions().corner4x),
color = color
) {
Text(
color = Color.White,
style = MaterialTheme.wireTypography.label01,
modifier = Modifier.padding(dimensions().spacing4x),
text = name,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
ConstraintLayout(modifier = modifier) {
val (nameLabel, connectingLabel) = createRefs()

Surface(
modifier = Modifier.constrainAs(nameLabel) {
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(connectingLabel.start)
},
shape = RoundedCornerShape(
topStart = dimensions().corner4x,
bottomStart = dimensions().corner4x,
topEnd = if (hasEstablishedAudio) dimensions().corner4x else 0.dp,
bottomEnd = if (hasEstablishedAudio) dimensions().corner4x else 0.dp,
),
color = color
) {
Text(
color = nameLabelColor,
style = MaterialTheme.wireTypography.label01,
modifier = Modifier.padding(dimensions().spacing4x),
text = name,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (!hasEstablishedAudio) {
Surface(
modifier = Modifier.constrainAs(connectingLabel) {
start.linkTo(nameLabel.end)
top.linkTo(nameLabel.top)
bottom.linkTo(nameLabel.bottom)
},
shape = RoundedCornerShape(
topEnd = dimensions().corner4x,
bottomEnd = dimensions().corner4x
),
color = color
) {
Text(
color = colorsScheme().error,
style = MaterialTheme.wireTypography.label01,
modifier = Modifier.padding(
top = dimensions().spacing4x,
bottom = dimensions().spacing4x,
end = dimensions().spacing4x
),
text = stringResource(id = R.string.participant_tile_call_connecting_label),
maxLines = 1,
)
}
}
}
}

@Composable
private fun MicrophoneTile(
modifier: Modifier,
isMuted: Boolean,
hasEstablishedAudio: Boolean
) {
if (isMuted) {
if (isMuted && hasEstablishedAudio) {
Surface(
modifier = modifier,
color = Color.Black,
Expand All @@ -319,21 +368,70 @@ private fun MicrophoneTile(
}
}

@Preview
@Preview("Default view")
@Composable
fun PreviewParticipantTile() {
ParticipantTile(
modifier = Modifier.height(300.dp),
participantTitleState = UICallParticipant(
id = QualifiedID("", ""),
clientId = "client-id",
name = "name",
name = "user name",
isMuted = true,
isSpeaking = false,
isCameraOn = false,
isSharingScreen = false,
avatar = null,
membership = Membership.Admin,
hasEstablishedAudio = true
),
onClearSelfUserVideoPreview = {},
onSelfUserVideoPreviewCreated = {},
isSelfUser = false
)
}

@Preview
@Composable
fun PreviewParticipantTalking() {
ParticipantTile(
modifier = Modifier.height(300.dp),
participantTitleState = UICallParticipant(
id = QualifiedID("", ""),
clientId = "client-id",
name = "long user name to be displayed in participant tile during a call",
isMuted = false,
isSpeaking = true,
isCameraOn = true,
isCameraOn = false,
isSharingScreen = false,
avatar = null,
membership = Membership.Admin,
hasEstablishedAudio = true
),
onClearSelfUserVideoPreview = {},
onSelfUserVideoPreviewCreated = {},
isSelfUser = false
)
}

@Preview
@Composable
fun PreviewParticipantConnecting() {
ParticipantTile(
modifier = Modifier
.height(350.dp)
.width(200.dp),
participantTitleState = UICallParticipant(
id = QualifiedID("", ""),
clientId = "client-id",
name = "Oussama2",
isMuted = true,
isSpeaking = false,
isCameraOn = false,
isSharingScreen = false,
avatar = null,
membership = Membership.Admin
membership = Membership.Admin,
hasEstablishedAudio = false
),
onClearSelfUserVideoPreview = {},
onSelfUserVideoPreviewCreated = {},
Expand Down
Expand Up @@ -110,20 +110,18 @@ fun GroupCallGrid(
isCameraOn = isCameraOn,
isSharingScreen = participant.isSharingScreen,
avatar = participant.avatar,
membership = participant.membership
membership = participant.membership,
hasEstablishedAudio = participant.hasEstablishedAudio
)
val tileHeight = (contentHeight - dimensions().spacing4x) / numberOfTilesRows

ParticipantTile(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures(
onPress = { /* Called when the gesture starts */ },
onDoubleTap = {
onDoubleTap(participantState.id, participantState.clientId, isSelfUser)
},
onLongPress = { /* Called on Long Press */ },
onTap = { /* Called on Tap */ }
}
)
}
.height(tileHeight)
Expand Down Expand Up @@ -177,6 +175,7 @@ fun PreviewGroupCallGrid() {
isSharingScreen = false,
avatar = null,
membership = Membership.Admin,
hasEstablishedAudio = true
),
UICallParticipant(
id = QualifiedID("", ""),
Expand All @@ -188,6 +187,7 @@ fun PreviewGroupCallGrid() {
isSharingScreen = false,
avatar = null,
membership = Membership.Admin,
hasEstablishedAudio = true
)
),
contentHeight = 800.dp,
Expand Down
Expand Up @@ -94,7 +94,8 @@ fun CallingHorizontalView(
isCameraOn = isCameraOn,
isSharingScreen = participant.isSharingScreen,
avatar = participant.avatar,
membership = participant.membership
membership = participant.membership,
hasEstablishedAudio = participant.hasEstablishedAudio
)

val tileHeight = (contentHeight - dimensions().spacing4x) / participants.size
Expand All @@ -103,12 +104,9 @@ fun CallingHorizontalView(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures(
onPress = { /* Called when the gesture starts */ },
onDoubleTap = {
onDoubleTap(participantState.id, participantState.clientId, isSelfUser)
},
onLongPress = { /* Called on Long Press */ },
onTap = { /* Called on Tap */ }
}
)
}
.fillMaxWidth()
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Expand Up @@ -1150,7 +1150,7 @@

<!-- Record Audio -->
<string name="record_audio_start_label">Start Recording</string>
<string name="record_audio_recording_label">Recording Audio...</string>
<string name="record_audio_recording_label">Recording Audio</string>
<string name="record_audio_send_label">Send Audio Message</string>
<string name="record_audio_discard_dialog_title">Discard Audio Message?</string>
<string name="record_audio_discard_dialog_text">The audio message will be deleted and can not be sent.</string>
Expand All @@ -1165,6 +1165,7 @@
<string name="last_message_composite_with_missing_text">sent an interactive message</string>
<string name="join_conversation_dialog_password_label">Conversation Password</string>
<string name="join_conversation_dialog_password_placeholder">Enter password</string>
<string name="participant_tile_call_connecting_label">Connecting…</string>
<string name="create_guest_link">Create Guest Link</string>
<string name="create_guest_link_with_password">Create password secured link</string>
<string name="create_guest_link_without_password_title">Create link without password</string>
Expand Down
Expand Up @@ -22,7 +22,6 @@ package com.wire.android.mapper

import com.wire.kalium.logic.data.call.Participant
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.user.UserAssetId
import io.mockk.MockKAnnotations
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
Expand All @@ -32,18 +31,17 @@ class UICallParticipantMapperTest {

@Test
fun givenParticipant_whenMappingToUICallParticipant_thenCorrectValuesShouldBeReturned() = runTest {
val (arrangement, mapper) = Arrangement().arrange()
val (_, mapper) = Arrangement().arrange()
// Given
val item = Participant(
QualifiedID("idvalue", "iddomain"),
"clientId",
"name",
false,
false,
false,
false,
true,
UserAssetId("assetvalue", "assetdomain")
id = QualifiedID("value", "domain"),
clientId = "clientId",
name = "name",
isMuted = false,
isCameraOn = false,
isSpeaking = false,
isSharingScreen = false,
hasEstablishedAudio = true,
)
// When
val result = mapper.toUICallParticipant(item)
Expand Down
Expand Up @@ -127,7 +127,8 @@ class OngoingCallViewModelTest {
isSpeaking = false,
isCameraOn = true,
isSharingScreen = false,
membership = Membership.None
membership = Membership.None,
hasEstablishedAudio = true
)
private val participant2 = UICallParticipant(
id = QualifiedID("value2", "domain"),
Expand All @@ -137,7 +138,8 @@ class OngoingCallViewModelTest {
isSpeaking = false,
isCameraOn = false,
isSharingScreen = false,
membership = Membership.None
membership = Membership.None,
hasEstablishedAudio = true
)
private val participant3 = UICallParticipant(
id = QualifiedID("value3", "domain"),
Expand All @@ -147,7 +149,8 @@ class OngoingCallViewModelTest {
isSpeaking = false,
isCameraOn = true,
isSharingScreen = true,
membership = Membership.None
membership = Membership.None,
hasEstablishedAudio = true
)
val participants = listOf(participant1, participant2, participant3)
}
Expand Down

0 comments on commit 98a9f25

Please sign in to comment.