diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt
index b8e7f21d4a..73b8ea9841 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt
@@ -6,9 +6,12 @@
package org.thoughtcrime.securesms.components.webrtc.controls
import android.content.Context
+import android.content.res.Configuration
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
@@ -29,8 +32,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rxjava3.subscribeAsState
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -46,13 +51,19 @@ import androidx.lifecycle.toLiveData
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Observable
+import org.signal.core.ui.Dialogs
import org.signal.core.ui.Dividers
+import org.signal.core.ui.Previews
import org.signal.core.ui.Rows
+import org.signal.core.ui.theme.LocalExtendedColors
import org.signal.core.ui.theme.SignalTheme
import org.signal.ringrtc.CallLinkState
import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar
+import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarImage
import org.thoughtcrime.securesms.components.AvatarImageView
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel
+import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.events.CallParticipant
import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent
@@ -259,6 +270,15 @@ private fun CallInfo(
onBlockClicked = onBlock
)
}
+
+ if (participantsState.inCallLobby && participantsState.unknownParticipantCount > 0) {
+ item {
+ UnknownMembersRow(
+ unknownMemberCount = participantsState.unknownParticipantCount,
+ allCallMembersAreUnknown = participantsState.participantsForList.isEmpty()
+ )
+ }
+ }
} else if (participantsState.isGroupCall()) {
items(
items = participantsState.groupMembers,
@@ -516,6 +536,129 @@ private fun GroupMemberRow(
) {}
}
+@Composable
+private fun UnknownMembersRow(
+ unknownMemberCount: Int,
+ allCallMembersAreUnknown: Boolean
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(Rows.defaultPadding())
+ ) {
+ when (unknownMemberCount) {
+ 1 -> SingleUnknownAvatar()
+ 2 -> TwoUnknownAvatars()
+ else -> ThreeUnknownAvatars()
+ }
+
+ val textResId = if (allCallMembersAreUnknown) {
+ R.plurals.CallInfoView__d_people
+ } else {
+ R.plurals.CallInfoView__plus_d_people
+ }
+
+ Text(
+ text = pluralStringResource(
+ id = textResId,
+ count = unknownMemberCount,
+ unknownMemberCount
+ ),
+ modifier = Modifier
+ .weight(1f)
+ .align(Alignment.CenterVertically)
+ .padding(horizontal = 24.dp)
+ )
+
+ var displayDialog by remember { mutableStateOf(false) }
+
+ Icon(
+ painter = painterResource(id = R.drawable.symbol_info_24),
+ contentDescription = stringResource(id = R.string.CallInfoView__more_information),
+ modifier = Modifier.clickable(onClick = {
+ displayDialog = true
+ })
+ )
+
+ if (displayDialog) {
+ Dialogs.SimpleMessageDialog(
+ message = stringResource(id = R.string.CallInfoView__before_joining_a_call),
+ dismiss = stringResource(id = R.string.CallInfoView__got_it),
+ onDismiss = { displayDialog = false }
+ )
+ }
+ }
+}
+
+@Composable
+private fun SingleUnknownAvatar() {
+ FallbackAvatarImage(
+ fallbackAvatar = FallbackAvatar.Resource.Person(AvatarColor.random()),
+ modifier = Modifier.size(40.dp)
+ )
+}
+
+@Composable
+private fun TwoUnknownAvatars() {
+ Box(modifier = Modifier.width(40.dp)) {
+ FallbackAvatarImage(
+ fallbackAvatar = FallbackAvatar.Resource.Person(AvatarColor.random()),
+ modifier = Modifier
+ .size(34.dp)
+ .align(Alignment.CenterStart)
+ )
+
+ FallbackAvatarImage(
+ fallbackAvatar = FallbackAvatar.Resource.Person(AvatarColor.random()),
+ modifier = Modifier
+ .size(38.dp)
+ .align(Alignment.CenterEnd)
+ .border(width = 2.dp, color = LocalExtendedColors.current.colorSurface1, shape = CircleShape)
+ )
+ }
+}
+
+@Composable
+private fun ThreeUnknownAvatars() {
+ Box(modifier = Modifier.width(40.dp)) {
+ FallbackAvatarImage(
+ fallbackAvatar = FallbackAvatar.Resource.Person(AvatarColor.random()),
+ modifier = Modifier
+ .size(27.dp)
+ .align(Alignment.CenterStart)
+ )
+
+ FallbackAvatarImage(
+ fallbackAvatar = FallbackAvatar.Resource.Person(AvatarColor.random()),
+ modifier = Modifier
+ .size(31.dp)
+ .align(Alignment.Center)
+ .border(width = 2.dp, color = SignalTheme.colors.colorSurface1, shape = CircleShape)
+ )
+
+ FallbackAvatarImage(
+ fallbackAvatar = FallbackAvatar.Resource.Person(AvatarColor.random()),
+ modifier = Modifier
+ .size(31.dp)
+ .align(Alignment.CenterEnd)
+ .border(width = 2.dp, color = SignalTheme.colors.colorSurface1, shape = CircleShape)
+ )
+ }
+}
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+private fun UnknownMembersRowPreview() {
+ Previews.BottomSheetPreview {
+ Column {
+ UnknownMembersRow(unknownMemberCount = 1, allCallMembersAreUnknown = true)
+ UnknownMembersRow(unknownMemberCount = 1, allCallMembersAreUnknown = false)
+ UnknownMembersRow(unknownMemberCount = 2, allCallMembersAreUnknown = false)
+ UnknownMembersRow(unknownMemberCount = 3, allCallMembersAreUnknown = false)
+ }
+ }
+}
+
private data class ParticipantsState(
val inCallLobby: Boolean = false,
val ringGroup: Boolean = true,
@@ -532,7 +675,9 @@ private data class ParticipantsState(
listOf(localParticipant) + remoteParticipants
} else {
remoteParticipants
- }
+ }.filter { it.recipient.isProfileSharing }
+
+ val unknownParticipantCount = remoteParticipants.count { !it.recipient.isProfileSharing }
val participantCountForDisplay: Int = if (participantCount == 0) {
participantsForList.size
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7c3bfeae89..1011a8c4d5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -221,6 +221,24 @@
Medium
Small
+
+
+
+ - %1$d person
+ - %1$d people
+
+
+
+ - +%1$d person
+ - +%1$d people
+
+
+ More information
+
+ Before joining a call you can only see the names of phone contacts, people you\'re in a group with, or people you\'ve chatted with 1:1. You\'ll see all names and photos once you\'ve joined the call.
+
+ Got it
+
Video recording is not supported on your device