Skip to content

Commit 5b9da3c

Browse files
bmartyElementBot
andauthored
Hide Element Call entry point if Element Call service is not available. (#4783)
* Hide Element Call entry point if Element Call service is not available. * No need to preview the case RoomCallState.Unavailable * Hide start call action from user profile if Element Call is not available. * Add mising `use` and cover the problem by a test. * Update screenshots * Update enterprise submodule ref. * Ensure `enterpriseService.isElementCallAvailable()` is not called several times. And fix unit tests on CI --------- Co-authored-by: ElementBot <android@element.io>
1 parent 730fb68 commit 5b9da3c

File tree

17 files changed

+146
-45
lines changed

17 files changed

+146
-45
lines changed

enterprise

features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ interface EnterpriseService {
1616
fun defaultHomeserverList(): List<String>
1717
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
1818

19+
suspend fun isElementCallAvailable(): Boolean
20+
1921
fun semanticColorsLight(): SemanticColors
2022
fun semanticColorsDark(): SemanticColors
2123

features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class DefaultEnterpriseService @Inject constructor() : EnterpriseService {
2525
override fun defaultHomeserverList(): List<String> = emptyList()
2626
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
2727

28+
override suspend fun isElementCallAvailable(): Boolean = true
29+
2830
override fun semanticColorsLight(): SemanticColors = compoundColorsLight
2931

3032
override fun semanticColorsDark(): SemanticColors = compoundColorsDark

features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class FakeEnterpriseService(
1818
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
1919
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
2020
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
21+
private val isElementCallAvailableResult: () -> Boolean = { lambdaError() },
2122
private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
2223
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
2324
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
@@ -35,6 +36,10 @@ class FakeEnterpriseService(
3536
isAllowedToConnectToHomeserverResult(homeserverUrl)
3637
}
3738

39+
override suspend fun isElementCallAvailable(): Boolean = simulateLongTask {
40+
isElementCallAvailableResult()
41+
}
42+
3843
override fun semanticColorsLight(): SemanticColors {
3944
return semanticColorsLightResult()
4045
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ internal fun CallMenuItem(
3838
modifier: Modifier = Modifier,
3939
) {
4040
when (roomCallState) {
41+
RoomCallState.Unavailable -> {
42+
Box(modifier)
43+
}
4144
is RoomCallState.StandBy -> {
4245
StandByCallMenuItem(
4346
roomCallState = roomCallState,

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,18 @@ internal fun TimelineItemCallNotifyView(
103103

104104
@PreviewsDayNight
105105
@Composable
106-
internal fun TimelineItemCallNotifyViewPreview() {
107-
ElementPreview {
108-
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
109-
RoomCallStateProvider().values.forEach { roomCallState ->
106+
internal fun TimelineItemCallNotifyViewPreview() = ElementPreview {
107+
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
108+
RoomCallStateProvider()
109+
.values
110+
.filter { it !is RoomCallState.Unavailable }
111+
.forEach { roomCallState ->
110112
TimelineItemCallNotifyView(
111113
event = aTimelineItemEvent(content = TimelineItemCallNotifyContent()),
112114
roomCallState = roomCallState,
113115
onLongClick = {},
114116
onJoinCallClick = {},
115117
)
116118
}
117-
}
118119
}
119120
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import io.element.android.features.roomcall.api.RoomCallState.StandBy
1313

1414
@Immutable
1515
sealed interface RoomCallState {
16+
data object Unavailable : RoomCallState
17+
1618
data class StandBy(
1719
val canStartCall: Boolean,
1820
) : RoomCallState
@@ -25,6 +27,7 @@ sealed interface RoomCallState {
2527
}
2628

2729
fun RoomCallState.hasPermissionToJoin() = when (this) {
30+
RoomCallState.Unavailable -> false
2831
is StandBy -> canStartCall
2932
is OnGoing -> canJoinCall
3033
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ open class RoomCallStateProvider : PreviewParameterProvider<RoomCallState> {
1616
anOngoingCallState(),
1717
anOngoingCallState(canJoinCall = false),
1818
anOngoingCallState(canJoinCall = true, isUserInTheCall = true),
19+
RoomCallState.Unavailable,
1920
)
2021
}
2122

features/roomcall/impl/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies {
2121
api(projects.features.roomcall.api)
2222
implementation(libs.kotlinx.collections.immutable)
2323
implementation(projects.features.call.api)
24+
implementation(projects.features.enterprise.api)
2425
implementation(projects.libraries.architecture)
2526
implementation(projects.libraries.matrix.api)
2627
implementation(projects.libraries.matrixui)
@@ -32,6 +33,7 @@ dependencies {
3233
testImplementation(libs.test.turbine)
3334
testImplementation(projects.libraries.matrix.test)
3435
testImplementation(projects.features.call.test)
36+
testImplementation(projects.features.enterprise.test)
3537
testImplementation(projects.tests.testutils)
3638
testImplementation(libs.androidx.compose.ui.test.junit)
3739
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import androidx.compose.runtime.Composable
1111
import androidx.compose.runtime.collectAsState
1212
import androidx.compose.runtime.derivedStateOf
1313
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.produceState
1415
import androidx.compose.runtime.remember
1516
import io.element.android.features.call.api.CurrentCall
1617
import io.element.android.features.call.api.CurrentCallService
18+
import io.element.android.features.enterprise.api.EnterpriseService
1719
import io.element.android.features.roomcall.api.RoomCallState
1820
import io.element.android.libraries.architecture.Presenter
1921
import io.element.android.libraries.matrix.api.room.JoinedRoom
@@ -23,9 +25,13 @@ import javax.inject.Inject
2325
class RoomCallStatePresenter @Inject constructor(
2426
private val room: JoinedRoom,
2527
private val currentCallService: CurrentCallService,
28+
private val enterpriseService: EnterpriseService,
2629
) : Presenter<RoomCallState> {
2730
@Composable
2831
override fun present(): RoomCallState {
32+
val isAvailable by produceState(false) {
33+
value = enterpriseService.isElementCallAvailable()
34+
}
2935
val roomInfo by room.roomInfoFlow.collectAsState()
3036
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
3137
val canJoinCall by room.canCall(updateKey = syncUpdateFlow.value)
@@ -41,6 +47,7 @@ class RoomCallStatePresenter @Inject constructor(
4147
}
4248
}
4349
val callState = when {
50+
isAvailable.not() -> RoomCallState.Unavailable
4451
roomInfo.hasRoomCall -> RoomCallState.OnGoing(
4552
canJoinCall = canJoinCall,
4653
isUserInTheCall = isUserInTheCall,

features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.google.common.truth.Truth.assertThat
1111
import io.element.android.features.call.api.CurrentCall
1212
import io.element.android.features.call.api.CurrentCallService
1313
import io.element.android.features.call.test.FakeCurrentCallService
14+
import io.element.android.features.enterprise.test.FakeEnterpriseService
1415
import io.element.android.features.roomcall.api.RoomCallState
1516
import io.element.android.libraries.matrix.api.room.JoinedRoom
1617
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
@@ -25,12 +26,13 @@ class RoomCallStatePresenterTest {
2526
@Test
2627
fun `present - initial state`() = runTest {
2728
val room = FakeJoinedRoom(
28-
baseRoom = FakeBaseRoom(
29+
baseRoom = FakeBaseRoom(
2930
canUserJoinCallResult = { Result.success(false) },
3031
)
3132
)
3233
val presenter = createRoomCallStatePresenter(joinedRoom = room)
3334
presenter.test {
35+
skipItems(1)
3436
val initialState = awaitItem()
3537
assertThat(initialState).isEqualTo(
3638
RoomCallState.StandBy(
@@ -40,10 +42,29 @@ class RoomCallStatePresenterTest {
4042
}
4143
}
4244

45+
@Test
46+
fun `present - element call not available`() = runTest {
47+
val room = FakeJoinedRoom(
48+
baseRoom = FakeBaseRoom(
49+
canUserJoinCallResult = { Result.success(false) },
50+
)
51+
)
52+
val presenter = createRoomCallStatePresenter(
53+
joinedRoom = room,
54+
isElementCallAvailable = false,
55+
)
56+
presenter.test {
57+
val initialState = awaitItem()
58+
assertThat(initialState).isEqualTo(
59+
RoomCallState.Unavailable
60+
)
61+
}
62+
}
63+
4364
@Test
4465
fun `present - initial state - user can join call`() = runTest {
4566
val room = FakeJoinedRoom(
46-
baseRoom = FakeBaseRoom(
67+
baseRoom = FakeBaseRoom(
4768
canUserJoinCallResult = { Result.success(true) },
4869
)
4970
)
@@ -69,6 +90,7 @@ class RoomCallStatePresenterTest {
6990
)
7091
val presenter = createRoomCallStatePresenter(joinedRoom = room)
7192
presenter.test {
93+
skipItems(1)
7294
assertThat(awaitItem()).isEqualTo(
7395
RoomCallState.OnGoing(
7496
canJoinCall = false,
@@ -83,15 +105,15 @@ class RoomCallStatePresenterTest {
83105
fun `present - user has joined the call on another session`() = runTest {
84106
val room = FakeJoinedRoom(
85107
baseRoom = FakeBaseRoom(
86-
canUserJoinCallResult = { Result.success(true) },
87-
).apply {
88-
givenRoomInfo(
89-
aRoomInfo(
90-
hasRoomCall = true,
91-
activeRoomCallParticipants = listOf(sessionId),
108+
canUserJoinCallResult = { Result.success(true) },
109+
).apply {
110+
givenRoomInfo(
111+
aRoomInfo(
112+
hasRoomCall = true,
113+
activeRoomCallParticipants = listOf(sessionId),
114+
)
92115
)
93-
)
94-
}
116+
}
95117
)
96118
val presenter = createRoomCallStatePresenter(joinedRoom = room)
97119
presenter.test {
@@ -110,15 +132,15 @@ class RoomCallStatePresenterTest {
110132
fun `present - user has joined the call locally`() = runTest {
111133
val room = FakeJoinedRoom(
112134
baseRoom = FakeBaseRoom(
113-
canUserJoinCallResult = { Result.success(true) },
114-
).apply {
115-
givenRoomInfo(
116-
aRoomInfo(
117-
hasRoomCall = true,
118-
activeRoomCallParticipants = listOf(sessionId),
135+
canUserJoinCallResult = { Result.success(true) },
136+
).apply {
137+
givenRoomInfo(
138+
aRoomInfo(
139+
hasRoomCall = true,
140+
activeRoomCallParticipants = listOf(sessionId),
141+
)
119142
)
120-
)
121-
}
143+
}
122144
)
123145
val presenter = createRoomCallStatePresenter(
124146
joinedRoom = room,
@@ -140,15 +162,15 @@ class RoomCallStatePresenterTest {
140162
fun `present - user leaves the call`() = runTest {
141163
val room = FakeJoinedRoom(
142164
baseRoom = FakeBaseRoom(
143-
canUserJoinCallResult = { Result.success(true) },
144-
).apply {
145-
givenRoomInfo(
146-
aRoomInfo(
147-
hasRoomCall = true,
148-
activeRoomCallParticipants = listOf(sessionId),
165+
canUserJoinCallResult = { Result.success(true) },
166+
).apply {
167+
givenRoomInfo(
168+
aRoomInfo(
169+
hasRoomCall = true,
170+
activeRoomCallParticipants = listOf(sessionId),
171+
)
149172
)
150-
)
151-
}
173+
}
152174
)
153175
val currentCall = MutableStateFlow<CurrentCall>(CurrentCall.RoomCall(room.roomId))
154176
val currentCallService = FakeCurrentCallService(currentCall = currentCall)
@@ -203,10 +225,14 @@ class RoomCallStatePresenterTest {
203225
private fun createRoomCallStatePresenter(
204226
joinedRoom: JoinedRoom,
205227
currentCallService: CurrentCallService = FakeCurrentCallService(),
228+
isElementCallAvailable: Boolean = true,
206229
): RoomCallStatePresenter {
207230
return RoomCallStatePresenter(
208231
room = joinedRoom,
209232
currentCallService = currentCallService,
233+
enterpriseService = FakeEnterpriseService(
234+
isElementCallAvailableResult = { isElementCallAvailable },
235+
),
210236
)
211237
}
212238
}

features/userprofile/impl/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies {
3333
implementation(projects.libraries.androidutils)
3434
implementation(projects.libraries.mediaviewer.api)
3535
implementation(projects.features.call.api)
36+
implementation(projects.features.enterprise.api)
3637
implementation(projects.features.verifysession.api)
3738
api(projects.features.userprofile.api)
3839
api(projects.features.userprofile.shared)
@@ -49,6 +50,7 @@ dependencies {
4950
testImplementation(libs.test.robolectric)
5051
testImplementation(projects.libraries.matrix.test)
5152
testImplementation(projects.features.createroom.test)
53+
testImplementation(projects.features.enterprise.test)
5254
testImplementation(projects.tests.testutils)
5355
testImplementation(libs.androidx.compose.ui.test.junit)
5456
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)

features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import dagger.assisted.Assisted
2121
import dagger.assisted.AssistedFactory
2222
import dagger.assisted.AssistedInject
2323
import io.element.android.features.createroom.api.StartDMAction
24+
import io.element.android.features.enterprise.api.EnterpriseService
2425
import io.element.android.features.userprofile.api.UserProfileEvents
2526
import io.element.android.features.userprofile.api.UserProfileState
2627
import io.element.android.features.userprofile.api.UserProfileState.ConfirmationDialog
@@ -44,6 +45,7 @@ class UserProfilePresenter @AssistedInject constructor(
4445
@Assisted private val userId: UserId,
4546
private val client: MatrixClient,
4647
private val startDMAction: StartDMAction,
48+
private val enterpriseService: EnterpriseService,
4749
) : Presenter<UserProfileState> {
4850
@AssistedFactory
4951
interface Factory {
@@ -59,11 +61,21 @@ class UserProfilePresenter @AssistedInject constructor(
5961

6062
@Composable
6163
private fun getCanCall(roomId: RoomId?): State<Boolean> {
62-
return produceState(initialValue = false, roomId) {
63-
value = if (client.isMe(userId)) {
64-
false
65-
} else {
66-
roomId?.let { client.getRoom(it)?.canUserJoinCall(client.sessionId)?.getOrNull() == true }.orFalse()
64+
val isElementCallAvailable by produceState(initialValue = false, roomId) {
65+
value = enterpriseService.isElementCallAvailable()
66+
}
67+
68+
return produceState(initialValue = false, isElementCallAvailable, roomId) {
69+
value = when {
70+
isElementCallAvailable.not() -> false
71+
client.isMe(userId) -> false
72+
else ->
73+
roomId
74+
?.let { client.getRoom(it) }
75+
?.use { room ->
76+
room.canUserJoinCall(client.sessionId).getOrNull()
77+
}
78+
.orFalse()
6779
}
6880
}
6981
}

0 commit comments

Comments
 (0)