From 0a91184e4a131dbc66f390a850e762bce3ce2128 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 30 Nov 2023 10:47:48 +0100 Subject: [PATCH] Set a default power level to join calls in room (#1927) * Set a default power level to join calls. Also, create new rooms taking this power level into account. * Modify test to make sure we display the disabled state even when there is an ongoing call --------- Co-authored-by: ElementBot --- ...t-default-power-level-to-join-calls.bugfix | 1 + .../messages/impl/MessagesPresenter.kt | 23 +++++++++++++++---- .../features/messages/impl/MessagesState.kt | 9 ++++++-- .../messages/impl/MessagesStateProvider.kt | 8 ++++--- .../features/messages/impl/MessagesView.kt | 18 ++++++--------- .../messages/impl/MessagesPresenterTest.kt | 18 +++++++++++++++ .../libraries/matrix/api/room/MatrixRoom.kt | 2 ++ .../libraries/matrix/impl/RustMatrixClient.kt | 19 ++++++++++++++- .../matrix/impl/room/RustMatrixRoom.kt | 6 +++++ .../matrix/test/room/FakeMatrixRoom.kt | 9 ++++++++ ...esView-Day-0_0_null_12,NEXUS_5,1.0,en].png | 3 +++ ...View-Night-0_1_null_12,NEXUS_5,1.0,en].png | 3 +++ 12 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 changelog.d/+set-default-power-level-to-join-calls.bugfix create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_12,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_12,NEXUS_5,1.0,en].png diff --git a/changelog.d/+set-default-power-level-to-join-calls.bugfix b/changelog.d/+set-default-power-level-to-join-calls.bugfix new file mode 100644 index 0000000000..fa143aa850 --- /dev/null +++ b/changelog.d/+set-default-power-level-to-join-calls.bugfix @@ -0,0 +1 @@ +Set a default power level to join calls. Also, create new rooms taking this power level into account. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index c7d4d8c9cc..a765a395a3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -79,6 +79,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.room.canRedactAsState @@ -108,6 +109,7 @@ class MessagesPresenter @AssistedInject constructor( private val featureFlagsService: FeatureFlagService, @Assisted private val navigator: MessagesNavigator, private val buildMeta: BuildMeta, + private val currentSessionIdHolder: CurrentSessionIdHolder, ) : Presenter { private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator) @@ -144,6 +146,16 @@ class MessagesPresenter @AssistedInject constructor( mutableStateOf(false) } + var canJoinCall by rememberSaveable { + mutableStateOf(false) + } + + LaunchedEffect(currentSessionIdHolder.current) { + withContext(dispatchers.io) { + canJoinCall = room.canUserJoinCall(userId = currentSessionIdHolder.current).getOrDefault(false) + } + } + val inviteProgress = remember { mutableStateOf>(Async.Uninitialized) } var showReinvitePrompt by remember { mutableStateOf(false) } LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow) { @@ -162,8 +174,6 @@ class MessagesPresenter @AssistedInject constructor( val enableTextFormatting by preferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true) var enableVoiceMessages by remember { mutableStateOf(false) } - // TODO add min power level to use this feature in the future? - val enableInRoomCalls = true LaunchedEffect(featureFlagsService) { enableVoiceMessages = featureFlagsService.isFeatureEnabled(FeatureFlags.VoiceMessages) } @@ -193,6 +203,12 @@ class MessagesPresenter @AssistedInject constructor( } } + val callState = when { + !canJoinCall -> RoomCallState.DISABLED + roomInfo?.hasRoomCall == true -> RoomCallState.ONGOING + else -> RoomCallState.ENABLED + } + return MessagesState( roomId = room.roomId, roomName = roomName, @@ -213,9 +229,8 @@ class MessagesPresenter @AssistedInject constructor( inviteProgress = inviteProgress.value, enableTextFormatting = enableTextFormatting, enableVoiceMessages = enableVoiceMessages, - enableInRoomCalls = enableInRoomCalls, appName = buildMeta.applicationName, - isCallOngoing = roomInfo?.hasRoomCall ?: false, + callState = callState, eventSink = { handleEvents(it) } ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 325c695988..7d342ab107 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -51,8 +51,13 @@ data class MessagesState( val showReinvitePrompt: Boolean, val enableTextFormatting: Boolean, val enableVoiceMessages: Boolean, - val enableInRoomCalls: Boolean, - val isCallOngoing: Boolean, + val callState: RoomCallState, val appName: String, val eventSink: (MessagesEvents) -> Unit ) + +enum class RoomCallState { + ENABLED, + ONGOING, + DISABLED +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 720c385f89..00c15fb410 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -66,7 +66,7 @@ open class MessagesStateProvider : PreviewParameterProvider { ), ), aMessagesState().copy( - isCallOngoing = true, + callState = RoomCallState.ONGOING, ), aMessagesState().copy( enableVoiceMessages = true, @@ -75,6 +75,9 @@ open class MessagesStateProvider : PreviewParameterProvider { showSendFailureDialog = true ), ), + aMessagesState().copy( + callState = RoomCallState.DISABLED, + ), ) } @@ -117,8 +120,7 @@ fun aMessagesState() = MessagesState( showReinvitePrompt = false, enableTextFormatting = true, enableVoiceMessages = true, - enableInRoomCalls = true, - isCallOngoing = false, + callState = RoomCallState.ENABLED, appName = "Element", eventSink = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index d7e4bfedc9..3a26079a3d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -191,10 +191,9 @@ fun MessagesView( MessagesViewTopBar( roomName = state.roomName.dataOrNull(), roomAvatar = state.roomAvatar.dataOrNull(), - inRoomCallsEnabled = state.enableInRoomCalls, + callState = state.callState, onBackPressed = onBackPressed, onRoomDetailsClicked = onRoomDetailsClicked, - isCallOngoing = state.isCallOngoing, onJoinCallClicked = onJoinCallClicked, ) } @@ -449,8 +448,7 @@ private fun MessagesViewComposerBottomSheetContents( private fun MessagesViewTopBar( roomName: String?, roomAvatar: AvatarData?, - inRoomCallsEnabled: Boolean, - isCallOngoing: Boolean, + callState: RoomCallState, onRoomDetailsClicked: () -> Unit, onJoinCallClicked: () -> Unit, onBackPressed: () -> Unit, @@ -477,13 +475,11 @@ private fun MessagesViewTopBar( } }, actions = { - if (inRoomCallsEnabled) { - if (isCallOngoing) { - JoinCallMenuItem(onJoinCallClicked = onJoinCallClicked) - } else { - IconButton(onClick = onJoinCallClicked) { - Icon(CompoundIcons.VideoCall, contentDescription = stringResource(CommonStrings.a11y_start_call)) - } + if (callState == RoomCallState.ONGOING) { + JoinCallMenuItem(onJoinCallClicked = onJoinCallClicked) + } else { + IconButton(onClick = onJoinCallClicked, enabled = callState != RoomCallState.DISABLED) { + Icon(CompoundIcons.VideoCall, contentDescription = stringResource(CommonStrings.a11y_start_call)) } } Spacer(Modifier.width(8.dp)) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 2f2709c8aa..1bed98ae06 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -99,6 +99,7 @@ import org.junit.Rule import org.junit.Test import kotlin.time.Duration.Companion.milliseconds +@Suppress("LargeClass") class MessagesPresenterTest { @get:Rule @@ -126,6 +127,21 @@ class MessagesPresenterTest { } } + @Test + fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest { + val room = FakeMatrixRoom().apply { + givenCanUserJoinCall(Result.success(false)) + givenRoomInfo(aRoomInfo(hasRoomCall = true)) + } + val presenter = createMessagesPresenter(matrixRoom = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = consumeItemsUntilTimeout().last() + assertThat(initialState.callState).isEqualTo(RoomCallState.DISABLED) + } + } + @Test fun `present - handle toggling a reaction`() = runTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) @@ -656,6 +672,7 @@ class MessagesPresenterTest { clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), + currentSessionIdHolder: CurrentSessionIdHolder = CurrentSessionIdHolder(FakeMatrixClient(A_SESSION_ID)), ): MessagesPresenter { val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom) val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) @@ -724,6 +741,7 @@ class MessagesPresenterTest { featureFlagsService = FakeFeatureFlagService(), buildMeta = aBuildMeta(), dispatchers = coroutineDispatchers, + currentSessionIdHolder = currentSessionIdHolder, ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index f9fbffa4a6..d7c8d7f49c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -132,6 +132,8 @@ interface MatrixRoom : Closeable { suspend fun canUserTriggerRoomNotification(userId: UserId): Result + suspend fun canUserJoinCall(userId: UserId): Result + suspend fun updateAvatar(mimeType: String, data: ByteArray): Result suspend fun removeAvatar(): Result diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index ef08f32edf..971e41414d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -77,6 +77,7 @@ import org.matrix.rustcomponents.sdk.BackupState import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.NotificationProcessSetup +import org.matrix.rustcomponents.sdk.PowerLevels import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.TaskHandle @@ -275,6 +276,7 @@ class RustMatrixClient constructor( }, invite = createRoomParams.invite?.map { it.value }, avatar = createRoomParams.avatar, + powerLevelContentOverride = defaultRoomCreationPowerLevels, ) val roomId = RoomId(client.createRoom(rustParams)) @@ -297,7 +299,7 @@ class RustMatrixClient constructor( isDirect = true, visibility = RoomVisibility.PRIVATE, preset = RoomPreset.TRUSTED_PRIVATE_CHAT, - invite = listOf(userId) + invite = listOf(userId), ) return createRoom(createRoomParams) } @@ -482,3 +484,18 @@ class RustMatrixClient constructor( } } +private val defaultRoomCreationPowerLevels = PowerLevels( + usersDefault = null, + eventsDefault = null, + stateDefault = null, + ban = null, + kick = null, + redact = null, + invite = null, + notifications = null, + users = mapOf(), + events = mapOf( + "m.call.member" to 0, + "org.matrix.msc3401.call.member" to 0, + ) +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index bef5e7bce4..8ec2d2b7b8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -364,6 +364,12 @@ class RustMatrixRoom( } } + override suspend fun canUserJoinCall(userId: UserId): Result { + return runCatching { + innerRoom.canUserSendState(userId.value, StateEventType.ROOM_MEMBER_EVENT.map()) + } + } + override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo, progressCallback: ProgressCallback?): Result { return sendAttachment(listOf(file, thumbnailFile)) { innerTimeline.sendImage(file.path, thumbnailFile.path, imageInfo.map(), progressCallback?.toProgressWatcher()) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 67c150fa3f..f16fdfbe98 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -111,6 +111,7 @@ class FakeMatrixRoom( private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io") private var getWidgetDriverResult: Result = Result.success(FakeWidgetDriver()) private var canUserTriggerRoomNotificationResult: Result = Result.success(true) + private var canUserJoinCallResult: Result = Result.success(true) var sendMessageMentions = emptyList() val editMessageCalls = mutableListOf>() @@ -292,6 +293,10 @@ class FakeMatrixRoom( return canUserTriggerRoomNotificationResult } + override suspend fun canUserJoinCall(userId: UserId): Result { + return canUserJoinCallResult + } + override suspend fun sendImage( file: File, thumbnailFile: File, @@ -474,6 +479,10 @@ class FakeMatrixRoom( canUserTriggerRoomNotificationResult = result } + fun givenCanUserJoinCall(result: Result) { + canUserJoinCallResult = result + } + fun givenIgnoreResult(result: Result) { ignoreResult = result } diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_12,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8fb84edc76 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_12,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cabc1dec0b89061db2b9f5301371b839a458e7e5bb6c8bc5b4d2fd4fcc3a179 +size 54275 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_12,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ef549a2345 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_12,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da867f9dd0e7d21419ce52e2e0eabfc26583e9dfb1523613a932394c307b3e28 +size 52621