diff --git a/app/src/main/kotlin/com/wire/android/mapper/UICallParticipantMapper.kt b/app/src/main/kotlin/com/wire/android/mapper/UICallParticipantMapper.kt
index a638adc6c0..6dc244c97e 100644
--- a/app/src/main/kotlin/com/wire/android/mapper/UICallParticipantMapper.kt
+++ b/app/src/main/kotlin/com/wire/android/mapper/UICallParticipantMapper.kt
@@ -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
)
}
diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/model/UICallParticipant.kt b/app/src/main/kotlin/com/wire/android/ui/calling/model/UICallParticipant.kt
index 2162bd5d89..4d13b6ac6b 100644
--- a/app/src/main/kotlin/com/wire/android/ui/calling/model/UICallParticipant.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/calling/model/UICallParticipant.kt
@@ -34,4 +34,5 @@ data class UICallParticipant(
val isSharingScreen: Boolean,
val avatar: ImageAsset.UserAvatarAsset? = null,
val membership: Membership,
+ val hasEstablishedAudio: Boolean
)
diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt
index 6285ba936a..faaa59128e 100644
--- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/ParticipantTile.kt
@@ -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
@@ -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
@@ -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) }
@@ -106,6 +110,7 @@ fun ParticipantTile(
AvatarTile(
modifier = Modifier
.fillMaxSize()
+ .alpha(alpha)
.constrainAs(avatar) { },
avatar = UserAvatarData(participantTitleState.avatar),
avatarSize = avatarSize
@@ -178,7 +183,8 @@ fun ParticipantTile(
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
},
- isMuted = participantTitleState.isMuted
+ isMuted = participantTitleState.isMuted,
+ hasEstablishedAudio = participantTitleState.hasEstablishedAudio
)
UsernameTile(
@@ -191,7 +197,8 @@ fun ParticipantTile(
}
.widthIn(max = onGoingCallTileUsernameMaxWidth),
name = participantTitleState.name,
- isSpeaking = participantTitleState.isSpeaking
+ isSpeaking = participantTitleState.isSpeaking,
+ hasEstablishedAudio = participantTitleState.hasEstablishedAudio
)
}
TileBorder(participantTitleState.isSpeaking)
@@ -277,23 +284,64 @@ 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,
+ )
+ }
+ }
}
}
@@ -301,8 +349,9 @@ private fun UsernameTile(
private fun MicrophoneTile(
modifier: Modifier,
isMuted: Boolean,
+ hasEstablishedAudio: Boolean
) {
- if (isMuted) {
+ if (isMuted && hasEstablishedAudio) {
Surface(
modifier = modifier,
color = Color.Black,
@@ -319,7 +368,7 @@ private fun MicrophoneTile(
}
}
-@Preview
+@Preview("Default view")
@Composable
fun PreviewParticipantTile() {
ParticipantTile(
@@ -327,13 +376,62 @@ fun PreviewParticipantTile() {
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 = {},
diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt
index 828a2cfb11..79b1729fae 100644
--- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/gridview/CallingGridView.kt
@@ -110,7 +110,8 @@ 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
@@ -118,12 +119,9 @@ fun GroupCallGrid(
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)
@@ -177,6 +175,7 @@ fun PreviewGroupCallGrid() {
isSharingScreen = false,
avatar = null,
membership = Membership.Admin,
+ hasEstablishedAudio = true
),
UICallParticipant(
id = QualifiedID("", ""),
@@ -188,6 +187,7 @@ fun PreviewGroupCallGrid() {
isSharingScreen = false,
avatar = null,
membership = Membership.Admin,
+ hasEstablishedAudio = true
)
),
contentHeight = 800.dp,
diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/horizentalview/CallingHorizontalView.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/horizentalview/CallingHorizontalView.kt
index e73d10bf9f..bb4fbd2dbe 100644
--- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/horizentalview/CallingHorizontalView.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/participantsview/horizentalview/CallingHorizontalView.kt
@@ -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
@@ -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()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ffe2b17a33..e4be118613 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1150,7 +1150,7 @@
Start Recording
- Recording Audio...
+ Recording Audio…
Send Audio Message
Discard Audio Message?
The audio message will be deleted and can not be sent.
@@ -1165,6 +1165,7 @@
sent an interactive message
Conversation Password
Enter password
+ Connecting…
Create Guest Link
Create password secured link
Create link without password
diff --git a/app/src/test/kotlin/com/wire/android/mapper/UICallParticipantMapperTest.kt b/app/src/test/kotlin/com/wire/android/mapper/UICallParticipantMapperTest.kt
index a38a35d94d..d5158a1d90 100644
--- a/app/src/test/kotlin/com/wire/android/mapper/UICallParticipantMapperTest.kt
+++ b/app/src/test/kotlin/com/wire/android/mapper/UICallParticipantMapperTest.kt
@@ -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
@@ -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)
diff --git a/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt
index 87e12234ae..9a01dd7514 100644
--- a/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt
+++ b/app/src/test/kotlin/com/wire/android/ui/calling/OngoingCallViewModelTest.kt
@@ -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"),
@@ -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"),
@@ -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)
}