Ensure the user can join the call even if they has joined a call in another session.
This commit is contained in:
parent
2a35edb14a
commit
0bbb1ac23d
10 changed files with 116 additions and 1 deletions
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.api
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value for the local current call.
|
||||||
|
*/
|
||||||
|
sealed interface CurrentCall {
|
||||||
|
data object None : CurrentCall
|
||||||
|
|
||||||
|
data class RoomCall(
|
||||||
|
val roomId: RoomId,
|
||||||
|
) : CurrentCall
|
||||||
|
|
||||||
|
data class ExternalUrl(
|
||||||
|
val url: String,
|
||||||
|
) : CurrentCall
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.api
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
interface CurrentCallObserver {
|
||||||
|
/**
|
||||||
|
* The current call state flow, which will be updated when the active call changes.
|
||||||
|
* This value reflect the local state of the call. It is not updated if the user answers
|
||||||
|
* a call from another session.
|
||||||
|
*/
|
||||||
|
val currentCall: StateFlow<CurrentCall>
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.core.app.NotificationManagerCompat
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.appconfig.ElementCallConfig
|
import io.element.android.appconfig.ElementCallConfig
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallType
|
||||||
|
import io.element.android.features.call.api.CurrentCall
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
|
@ -82,6 +83,7 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
private val ringingCallNotificationCreator: RingingCallNotificationCreator,
|
private val ringingCallNotificationCreator: RingingCallNotificationCreator,
|
||||||
private val notificationManagerCompat: NotificationManagerCompat,
|
private val notificationManagerCompat: NotificationManagerCompat,
|
||||||
private val matrixClientProvider: MatrixClientProvider,
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
|
private val defaultCurrentCallObserver: DefaultCurrentCallObserver,
|
||||||
) : ActiveCallManager {
|
) : ActiveCallManager {
|
||||||
private var timedOutCallJob: Job? = null
|
private var timedOutCallJob: Job? = null
|
||||||
|
|
||||||
|
|
@ -89,6 +91,7 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observeRingingCall()
|
observeRingingCall()
|
||||||
|
observeCurrentCall()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun registerIncomingCall(notificationData: CallNotificationData) {
|
override fun registerIncomingCall(notificationData: CallNotificationData) {
|
||||||
|
|
@ -209,6 +212,28 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
}
|
}
|
||||||
.launchIn(coroutineScope)
|
.launchIn(coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeCurrentCall() {
|
||||||
|
activeCall
|
||||||
|
.onEach { value ->
|
||||||
|
if (value == null) {
|
||||||
|
defaultCurrentCallObserver.onCallEnded()
|
||||||
|
} else {
|
||||||
|
when (value.callState) {
|
||||||
|
is CallState.Ringing -> {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
is CallState.InCall -> {
|
||||||
|
when (val callType = value.callType) {
|
||||||
|
is CallType.ExternalUrl -> defaultCurrentCallObserver.onCallStarted(CurrentCall.ExternalUrl(callType.url))
|
||||||
|
is CallType.RoomCall -> defaultCurrentCallObserver.onCallStarted(CurrentCall.RoomCall(callType.roomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(coroutineScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.impl.utils
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.features.call.api.CurrentCall
|
||||||
|
import io.element.android.features.call.api.CurrentCallObserver
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultCurrentCallObserver @Inject constructor() : CurrentCallObserver {
|
||||||
|
override val currentCall = MutableStateFlow<CurrentCall>(CurrentCall.None)
|
||||||
|
|
||||||
|
fun onCallStarted(call: CurrentCall) {
|
||||||
|
currentCall.value = call
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCallEnded() {
|
||||||
|
currentCall.value = CurrentCall.None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ import io.element.android.features.call.impl.notifications.RingingCallNotificati
|
||||||
import io.element.android.features.call.impl.utils.ActiveCall
|
import io.element.android.features.call.impl.utils.ActiveCall
|
||||||
import io.element.android.features.call.impl.utils.CallState
|
import io.element.android.features.call.impl.utils.CallState
|
||||||
import io.element.android.features.call.impl.utils.DefaultActiveCallManager
|
import io.element.android.features.call.impl.utils.DefaultActiveCallManager
|
||||||
|
import io.element.android.features.call.impl.utils.DefaultCurrentCallObserver
|
||||||
import io.element.android.features.call.test.aCallNotificationData
|
import io.element.android.features.call.test.aCallNotificationData
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -299,5 +300,6 @@ class DefaultActiveCallManagerTest {
|
||||||
),
|
),
|
||||||
notificationManagerCompat = notificationManagerCompat,
|
notificationManagerCompat = notificationManagerCompat,
|
||||||
matrixClientProvider = matrixClientProvider,
|
matrixClientProvider = matrixClientProvider,
|
||||||
|
defaultCurrentCallObserver = DefaultCurrentCallObserver(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ private fun OnGoingCallMenuItem(
|
||||||
onJoinCallClick: () -> Unit,
|
onJoinCallClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (!roomCallState.isUserInTheCall) {
|
if (!roomCallState.isUserLocallyInTheCall) {
|
||||||
Button(
|
Button(
|
||||||
onClick = onJoinCallClick,
|
onClick = onJoinCallClick,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ sealed interface RoomCallState {
|
||||||
data class OnGoing(
|
data class OnGoing(
|
||||||
val canJoinCall: Boolean,
|
val canJoinCall: Boolean,
|
||||||
val isUserInTheCall: Boolean,
|
val isUserInTheCall: Boolean,
|
||||||
|
val isUserLocallyInTheCall: Boolean,
|
||||||
) : RoomCallState
|
) : RoomCallState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@ open class RoomCallStateProvider : PreviewParameterProvider<RoomCallState> {
|
||||||
fun anOngoingCallState(
|
fun anOngoingCallState(
|
||||||
canJoinCall: Boolean = true,
|
canJoinCall: Boolean = true,
|
||||||
isUserInTheCall: Boolean = false,
|
isUserInTheCall: Boolean = false,
|
||||||
|
isUserLocallyInTheCall: Boolean = isUserInTheCall,
|
||||||
) = RoomCallState.OnGoing(
|
) = RoomCallState.OnGoing(
|
||||||
canJoinCall = canJoinCall,
|
canJoinCall = canJoinCall,
|
||||||
isUserInTheCall = isUserInTheCall,
|
isUserInTheCall = isUserInTheCall,
|
||||||
|
isUserLocallyInTheCall = isUserLocallyInTheCall,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aStandByCallState(
|
fun aStandByCallState(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ setupAnvil()
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.roomcall.api)
|
api(projects.features.roomcall.api)
|
||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
|
implementation(projects.features.call.api)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import io.element.android.features.call.api.CurrentCall
|
||||||
|
import io.element.android.features.call.api.CurrentCallObserver
|
||||||
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.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
|
|
@ -20,6 +22,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class RoomCallStatePresenter @Inject constructor(
|
class RoomCallStatePresenter @Inject constructor(
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
|
private val currentCallObserver: CurrentCallObserver,
|
||||||
) : Presenter<RoomCallState> {
|
) : Presenter<RoomCallState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomCallState {
|
override fun present(): RoomCallState {
|
||||||
|
|
@ -31,10 +34,17 @@ class RoomCallStatePresenter @Inject constructor(
|
||||||
room.sessionId in roomInfo?.activeRoomCallParticipants.orEmpty()
|
room.sessionId in roomInfo?.activeRoomCallParticipants.orEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val currentCall by currentCallObserver.currentCall.collectAsState()
|
||||||
|
val isUserLocallyInTheCall by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
(currentCall as? CurrentCall.RoomCall)?.roomId == room.roomId
|
||||||
|
}
|
||||||
|
}
|
||||||
val callState = when {
|
val callState = when {
|
||||||
roomInfo?.hasRoomCall == true -> RoomCallState.OnGoing(
|
roomInfo?.hasRoomCall == true -> RoomCallState.OnGoing(
|
||||||
canJoinCall = canJoinCall,
|
canJoinCall = canJoinCall,
|
||||||
isUserInTheCall = isUserInTheCall,
|
isUserInTheCall = isUserInTheCall,
|
||||||
|
isUserLocallyInTheCall = isUserLocallyInTheCall,
|
||||||
)
|
)
|
||||||
else -> RoomCallState.StandBy(canStartCall = canJoinCall)
|
else -> RoomCallState.StandBy(canStartCall = canJoinCall)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue