feat: Default to camera muted when joining ongoing voice call
This commit is contained in:
parent
7a9260905e
commit
684d53b90e
6 changed files with 131 additions and 3 deletions
|
|
@ -60,6 +60,7 @@ import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
||||||
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
||||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
import io.element.android.libraries.matrix.ui.components.InviteSenderView
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
|
|
@ -349,6 +350,7 @@ private fun MessagePreviewAndIndicatorRow(
|
||||||
if (room.hasRoomCall) {
|
if (room.hasRoomCall) {
|
||||||
OnGoingCallIcon(
|
OnGoingCallIcon(
|
||||||
color = tint,
|
color = tint,
|
||||||
|
isAudio = room.activeCallIntent == CallIntent.AUDIO
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
if (room.userDefinedNotificationMode == RoomNotificationMode.MUTE) {
|
||||||
|
|
@ -398,10 +400,11 @@ private fun InviteNameAndIndicatorRow(
|
||||||
@Composable
|
@Composable
|
||||||
private fun OnGoingCallIcon(
|
private fun OnGoingCallIcon(
|
||||||
color: Color,
|
color: Color,
|
||||||
|
isAudio: Boolean
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
imageVector = CompoundIcons.VideoCallSolid(),
|
imageVector = if (isAudio) CompoundIcons.VoiceCallSolid() else CompoundIcons.VideoCallSolid(),
|
||||||
contentDescription = stringResource(CommonStrings.a11y_notifications_ongoing_call),
|
contentDescription = stringResource(CommonStrings.a11y_notifications_ongoing_call),
|
||||||
tint = color,
|
tint = color,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
||||||
|
import io.element.android.libraries.matrix.api.room.CallIntentConsensus
|
||||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
|
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
|
||||||
|
|
@ -50,6 +51,11 @@ class RoomListRoomSummaryFactory(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode,
|
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode,
|
||||||
hasRoomCall = roomInfo.hasRoomCall,
|
hasRoomCall = roomInfo.hasRoomCall,
|
||||||
|
activeCallIntent = when (val consensus = roomInfo.activeCallIntentConsensus) {
|
||||||
|
is CallIntentConsensus.Full -> consensus.callIntent
|
||||||
|
is CallIntentConsensus.Partial -> consensus.callIntent
|
||||||
|
CallIntentConsensus.None -> null
|
||||||
|
},
|
||||||
isDirect = roomInfo.isDirect,
|
isDirect = roomInfo.isDirect,
|
||||||
isFavorite = roomInfo.isFavorite,
|
isFavorite = roomInfo.isFavorite,
|
||||||
inviteSender = roomInfo.inviter?.toInviteSender(),
|
inviteSender = roomInfo.inviter?.toInviteSender(),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import io.element.android.features.invite.api.InviteData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -33,6 +34,7 @@ data class RoomListRoomSummary(
|
||||||
val avatarData: AvatarData,
|
val avatarData: AvatarData,
|
||||||
val userDefinedNotificationMode: RoomNotificationMode?,
|
val userDefinedNotificationMode: RoomNotificationMode?,
|
||||||
val hasRoomCall: Boolean,
|
val hasRoomCall: Boolean,
|
||||||
|
val activeCallIntent: CallIntent?,
|
||||||
val isDirect: Boolean,
|
val isDirect: Boolean,
|
||||||
val isDm: Boolean,
|
val isDm: Boolean,
|
||||||
val isFavorite: Boolean,
|
val isFavorite: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.ui.model.InviteSender
|
import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
@ -132,6 +133,14 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider<RoomListRoomSu
|
||||||
listOf(
|
listOf(
|
||||||
aRoomListRoomSummary(latestEvent = LatestEvent.Sending("A sending message")),
|
aRoomListRoomSummary(latestEvent = LatestEvent.Sending("A sending message")),
|
||||||
aRoomListRoomSummary(latestEvent = LatestEvent.Error),
|
aRoomListRoomSummary(latestEvent = LatestEvent.Error),
|
||||||
|
),
|
||||||
|
listOf(
|
||||||
|
aRoomListRoomSummary(
|
||||||
|
name = "Active voice call",
|
||||||
|
latestEvent = LatestEvent.Synced("No activity, call"),
|
||||||
|
hasRoomCall = true,
|
||||||
|
activeCallIntent = CallIntent.AUDIO
|
||||||
|
),
|
||||||
)
|
)
|
||||||
).flatten()
|
).flatten()
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +167,7 @@ internal fun aRoomListRoomSummary(
|
||||||
timestamp: String? = latestEvent.takeIf { it !is LatestEvent.None }?.let { "88:88" },
|
timestamp: String? = latestEvent.takeIf { it !is LatestEvent.None }?.let { "88:88" },
|
||||||
notificationMode: RoomNotificationMode? = null,
|
notificationMode: RoomNotificationMode? = null,
|
||||||
hasRoomCall: Boolean = false,
|
hasRoomCall: Boolean = false,
|
||||||
|
activeCallIntent: CallIntent? = null,
|
||||||
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
avatarData: AvatarData = AvatarData(id, name, size = AvatarSize.RoomListItem),
|
||||||
isDirect: Boolean = false,
|
isDirect: Boolean = false,
|
||||||
isDm: Boolean = false,
|
isDm: Boolean = false,
|
||||||
|
|
@ -181,6 +191,7 @@ internal fun aRoomListRoomSummary(
|
||||||
avatarData = avatarData,
|
avatarData = avatarData,
|
||||||
userDefinedNotificationMode = notificationMode,
|
userDefinedNotificationMode = notificationMode,
|
||||||
hasRoomCall = hasRoomCall,
|
hasRoomCall = hasRoomCall,
|
||||||
|
activeCallIntent = activeCallIntent,
|
||||||
isDirect = isDirect,
|
isDirect = isDirect,
|
||||||
isDm = isDm,
|
isDm = isDm,
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import io.element.android.features.call.api.CurrentCallService
|
||||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||||
import io.element.android.features.roomcall.api.RoomCallState
|
import io.element.android.features.roomcall.api.RoomCallState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
|
import io.element.android.libraries.matrix.api.room.CallIntentConsensus
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.isDm
|
import io.element.android.libraries.matrix.api.room.isDm
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.canCall
|
import io.element.android.libraries.matrix.api.room.powerlevels.canCall
|
||||||
|
|
@ -57,8 +59,7 @@ class RoomCallStatePresenter(
|
||||||
canJoinCall = canJoinCall,
|
canJoinCall = canJoinCall,
|
||||||
isUserInTheCall = isUserInTheCall,
|
isUserInTheCall = isUserInTheCall,
|
||||||
isUserLocallyInTheCall = isUserLocallyInTheCall,
|
isUserLocallyInTheCall = isUserLocallyInTheCall,
|
||||||
// TODO resolve intent while the call is ongoing
|
isAudioCall = roomInfo.activeCallIntentConsensus.isAudio(),
|
||||||
isAudioCall = false
|
|
||||||
)
|
)
|
||||||
else -> RoomCallState.StandBy(
|
else -> RoomCallState.StandBy(
|
||||||
canStartCall = canJoinCall,
|
canStartCall = canJoinCall,
|
||||||
|
|
@ -70,3 +71,12 @@ class RoomCallStatePresenter(
|
||||||
return callState
|
return callState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun CallIntentConsensus.isAudio(): Boolean {
|
||||||
|
val intent = when (this) {
|
||||||
|
is CallIntentConsensus.Full -> callIntent
|
||||||
|
is CallIntentConsensus.Partial -> callIntent
|
||||||
|
is CallIntentConsensus.None -> return false
|
||||||
|
}
|
||||||
|
return intent == CallIntent.AUDIO
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import io.element.android.features.call.api.CurrentCallService
|
||||||
import io.element.android.features.call.test.FakeCurrentCallService
|
import io.element.android.features.call.test.FakeCurrentCallService
|
||||||
import io.element.android.features.enterprise.test.FakeSessionEnterpriseService
|
import io.element.android.features.enterprise.test.FakeSessionEnterpriseService
|
||||||
import io.element.android.features.roomcall.api.RoomCallState
|
import io.element.android.features.roomcall.api.RoomCallState
|
||||||
|
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||||
|
import io.element.android.libraries.matrix.api.room.CallIntentConsensus
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||||
|
|
@ -188,6 +190,100 @@ class RoomCallStatePresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - active call with audio Intent`() = runTest {
|
||||||
|
val room = FakeJoinedRoom(
|
||||||
|
baseRoom = FakeBaseRoom(
|
||||||
|
roomPermissions = roomPermissions(true),
|
||||||
|
).apply {
|
||||||
|
givenRoomInfo(
|
||||||
|
aRoomInfo(
|
||||||
|
hasRoomCall = true,
|
||||||
|
activeCallIntentConsensus = CallIntentConsensus.Full(CallIntent.AUDIO),
|
||||||
|
activeRoomCallParticipants = emptyList(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val presenter = createRoomCallStatePresenter(
|
||||||
|
joinedRoom = room,
|
||||||
|
currentCallService = FakeCurrentCallService(MutableStateFlow(CurrentCall.RoomCall(room.roomId))),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
|
assertThat(awaitItem()).isEqualTo(
|
||||||
|
RoomCallState.OnGoing(
|
||||||
|
canJoinCall = true,
|
||||||
|
isAudioCall = true,
|
||||||
|
isUserInTheCall = false,
|
||||||
|
isUserLocallyInTheCall = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - active call with partial audio Intent`() = runTest {
|
||||||
|
val room = FakeJoinedRoom(
|
||||||
|
baseRoom = FakeBaseRoom(
|
||||||
|
roomPermissions = roomPermissions(true),
|
||||||
|
).apply {
|
||||||
|
givenRoomInfo(
|
||||||
|
aRoomInfo(
|
||||||
|
hasRoomCall = true,
|
||||||
|
activeCallIntentConsensus = CallIntentConsensus.Partial(CallIntent.AUDIO, 1, 4),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val presenter = createRoomCallStatePresenter(
|
||||||
|
joinedRoom = room,
|
||||||
|
currentCallService = FakeCurrentCallService(MutableStateFlow(CurrentCall.RoomCall(room.roomId))),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
|
assertThat(awaitItem()).isEqualTo(
|
||||||
|
RoomCallState.OnGoing(
|
||||||
|
canJoinCall = true,
|
||||||
|
isAudioCall = true,
|
||||||
|
isUserInTheCall = false,
|
||||||
|
isUserLocallyInTheCall = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - active call with no intent defaults to Audio`() = runTest {
|
||||||
|
val room = FakeJoinedRoom(
|
||||||
|
baseRoom = FakeBaseRoom(
|
||||||
|
roomPermissions = roomPermissions(true),
|
||||||
|
).apply {
|
||||||
|
givenRoomInfo(
|
||||||
|
aRoomInfo(
|
||||||
|
hasRoomCall = true,
|
||||||
|
activeCallIntentConsensus = CallIntentConsensus.None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val presenter = createRoomCallStatePresenter(
|
||||||
|
joinedRoom = room,
|
||||||
|
currentCallService = FakeCurrentCallService(MutableStateFlow(CurrentCall.RoomCall(room.roomId))),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
|
assertThat(awaitItem()).isEqualTo(
|
||||||
|
RoomCallState.OnGoing(
|
||||||
|
canJoinCall = true,
|
||||||
|
isAudioCall = false,
|
||||||
|
isUserInTheCall = false,
|
||||||
|
isUserLocallyInTheCall = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - user leaves the call`() = runTest {
|
fun `present - user leaves the call`() = runTest {
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue