Skip to content

Commit 1c78f96

Browse files
committed
Ensure the user can join the call even if they has joined a call in another session.
1 parent 58e6696 commit 1c78f96

File tree

10 files changed

+116
-1
lines changed

10 files changed

+116
-1
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.call.api
9+
10+
import io.element.android.libraries.matrix.api.core.RoomId
11+
12+
/**
13+
* Value for the local current call.
14+
*/
15+
sealed interface CurrentCall {
16+
data object None : CurrentCall
17+
18+
data class RoomCall(
19+
val roomId: RoomId,
20+
) : CurrentCall
21+
22+
data class ExternalUrl(
23+
val url: String,
24+
) : CurrentCall
25+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.call.api
9+
10+
import kotlinx.coroutines.flow.StateFlow
11+
12+
interface CurrentCallObserver {
13+
/**
14+
* The current call state flow, which will be updated when the active call changes.
15+
* This value reflect the local state of the call. It is not updated if the user answers
16+
* a call from another session.
17+
*/
18+
val currentCall: StateFlow<CurrentCall>
19+
}

features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.core.app.NotificationManagerCompat
1212
import com.squareup.anvil.annotations.ContributesBinding
1313
import io.element.android.appconfig.ElementCallConfig
1414
import io.element.android.features.call.api.CallType
15+
import io.element.android.features.call.api.CurrentCall
1516
import io.element.android.features.call.impl.notifications.CallNotificationData
1617
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
1718
import io.element.android.libraries.di.AppScope
@@ -82,13 +83,15 @@ class DefaultActiveCallManager @Inject constructor(
8283
private val ringingCallNotificationCreator: RingingCallNotificationCreator,
8384
private val notificationManagerCompat: NotificationManagerCompat,
8485
private val matrixClientProvider: MatrixClientProvider,
86+
private val defaultCurrentCallObserver: DefaultCurrentCallObserver,
8587
) : ActiveCallManager {
8688
private var timedOutCallJob: Job? = null
8789

8890
override val activeCall = MutableStateFlow<ActiveCall?>(null)
8991

9092
init {
9193
observeRingingCall()
94+
observeCurrentCall()
9295
}
9396

9497
override fun registerIncomingCall(notificationData: CallNotificationData) {
@@ -209,6 +212,28 @@ class DefaultActiveCallManager @Inject constructor(
209212
}
210213
.launchIn(coroutineScope)
211214
}
215+
216+
private fun observeCurrentCall() {
217+
activeCall
218+
.onEach { value ->
219+
if (value == null) {
220+
defaultCurrentCallObserver.onCallEnded()
221+
} else {
222+
when (value.callState) {
223+
is CallState.Ringing -> {
224+
// Nothing to do
225+
}
226+
is CallState.InCall -> {
227+
when (val callType = value.callType) {
228+
is CallType.ExternalUrl -> defaultCurrentCallObserver.onCallStarted(CurrentCall.ExternalUrl(callType.url))
229+
is CallType.RoomCall -> defaultCurrentCallObserver.onCallStarted(CurrentCall.RoomCall(callType.roomId))
230+
}
231+
}
232+
}
233+
}
234+
}
235+
.launchIn(coroutineScope)
236+
}
212237
}
213238

214239
/**
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.call.impl.utils
9+
10+
import com.squareup.anvil.annotations.ContributesBinding
11+
import io.element.android.features.call.api.CurrentCall
12+
import io.element.android.features.call.api.CurrentCallObserver
13+
import io.element.android.libraries.di.AppScope
14+
import io.element.android.libraries.di.SingleIn
15+
import kotlinx.coroutines.flow.MutableStateFlow
16+
import javax.inject.Inject
17+
18+
@SingleIn(AppScope::class)
19+
@ContributesBinding(AppScope::class)
20+
class DefaultCurrentCallObserver @Inject constructor() : CurrentCallObserver {
21+
override val currentCall = MutableStateFlow<CurrentCall>(CurrentCall.None)
22+
23+
fun onCallStarted(call: CurrentCall) {
24+
currentCall.value = call
25+
}
26+
27+
fun onCallEnded() {
28+
currentCall.value = CurrentCall.None
29+
}
30+
}

features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.element.android.features.call.impl.notifications.RingingCallNotificati
1515
import io.element.android.features.call.impl.utils.ActiveCall
1616
import io.element.android.features.call.impl.utils.CallState
1717
import io.element.android.features.call.impl.utils.DefaultActiveCallManager
18+
import io.element.android.features.call.impl.utils.DefaultCurrentCallObserver
1819
import io.element.android.features.call.test.aCallNotificationData
1920
import io.element.android.libraries.matrix.api.core.EventId
2021
import io.element.android.libraries.matrix.api.core.RoomId
@@ -299,5 +300,6 @@ class DefaultActiveCallManagerTest {
299300
),
300301
notificationManagerCompat = notificationManagerCompat,
301302
matrixClientProvider = matrixClientProvider,
303+
defaultCurrentCallObserver = DefaultCurrentCallObserver(),
302304
)
303305
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/CallMenuItem.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private fun OnGoingCallMenuItem(
7979
onJoinCallClick: () -> Unit,
8080
modifier: Modifier = Modifier,
8181
) {
82-
if (!roomCallState.isUserInTheCall) {
82+
if (!roomCallState.isUserLocallyInTheCall) {
8383
Button(
8484
onClick = onJoinCallClick,
8585
colors = ButtonDefaults.buttonColors(

features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ sealed interface RoomCallState {
2020
data class OnGoing(
2121
val canJoinCall: Boolean,
2222
val isUserInTheCall: Boolean,
23+
val isUserLocallyInTheCall: Boolean,
2324
) : RoomCallState
2425
}
2526

features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallStateProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ open class RoomCallStateProvider : PreviewParameterProvider<RoomCallState> {
2222
fun anOngoingCallState(
2323
canJoinCall: Boolean = true,
2424
isUserInTheCall: Boolean = false,
25+
isUserLocallyInTheCall: Boolean = isUserInTheCall,
2526
) = RoomCallState.OnGoing(
2627
canJoinCall = canJoinCall,
2728
isUserInTheCall = isUserInTheCall,
29+
isUserLocallyInTheCall = isUserLocallyInTheCall,
2830
)
2931

3032
fun aStandByCallState(

features/roomcall/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ setupAnvil()
2020
dependencies {
2121
api(projects.features.roomcall.api)
2222
implementation(libs.kotlinx.collections.immutable)
23+
implementation(projects.features.call.api)
2324
implementation(projects.libraries.architecture)
2425
implementation(projects.libraries.matrix.api)
2526
implementation(projects.libraries.matrixui)

features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import androidx.compose.runtime.collectAsState
1212
import androidx.compose.runtime.derivedStateOf
1313
import androidx.compose.runtime.getValue
1414
import androidx.compose.runtime.remember
15+
import io.element.android.features.call.api.CurrentCall
16+
import io.element.android.features.call.api.CurrentCallObserver
1517
import io.element.android.features.roomcall.api.RoomCallState
1618
import io.element.android.libraries.architecture.Presenter
1719
import io.element.android.libraries.matrix.api.room.MatrixRoom
@@ -20,6 +22,7 @@ import javax.inject.Inject
2022

2123
class RoomCallStatePresenter @Inject constructor(
2224
private val room: MatrixRoom,
25+
private val currentCallObserver: CurrentCallObserver,
2326
) : Presenter<RoomCallState> {
2427
@Composable
2528
override fun present(): RoomCallState {
@@ -31,10 +34,17 @@ class RoomCallStatePresenter @Inject constructor(
3134
room.sessionId in roomInfo?.activeRoomCallParticipants.orEmpty()
3235
}
3336
}
37+
val currentCall by currentCallObserver.currentCall.collectAsState()
38+
val isUserLocallyInTheCall by remember {
39+
derivedStateOf {
40+
(currentCall as? CurrentCall.RoomCall)?.roomId == room.roomId
41+
}
42+
}
3443
val callState = when {
3544
roomInfo?.hasRoomCall == true -> RoomCallState.OnGoing(
3645
canJoinCall = canJoinCall,
3746
isUserInTheCall = isUserInTheCall,
47+
isUserLocallyInTheCall = isUserLocallyInTheCall,
3848
)
3949
else -> RoomCallState.StandBy(canStartCall = canJoinCall)
4050
}

0 commit comments

Comments
 (0)