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 <benoitm+elementbot@element.io>
This commit is contained in:
committed by
GitHub
parent
b94ee9fb0a
commit
0a91184e4a
@@ -0,0 +1 @@
|
||||
Set a default power level to join calls. Also, create new rooms taking this power level into account.
|
||||
@@ -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<MessagesState> {
|
||||
|
||||
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<Unit>>(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) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||
),
|
||||
),
|
||||
aMessagesState().copy(
|
||||
isCallOngoing = true,
|
||||
callState = RoomCallState.ONGOING,
|
||||
),
|
||||
aMessagesState().copy(
|
||||
enableVoiceMessages = true,
|
||||
@@ -75,6 +75,9 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||
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 = {}
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,8 @@ interface MatrixRoom : Closeable {
|
||||
|
||||
suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean>
|
||||
|
||||
suspend fun canUserJoinCall(userId: UserId): Result<Boolean>
|
||||
|
||||
suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
|
||||
suspend fun removeAvatar(): Result<Unit>
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -364,6 +364,12 @@ class RustMatrixRoom(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun canUserJoinCall(userId: UserId): Result<Boolean> {
|
||||
return runCatching {
|
||||
innerRoom.canUserSendState(userId.value, StateEventType.ROOM_MEMBER_EVENT.map())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo, progressCallback: ProgressCallback?): Result<MediaUploadHandler> {
|
||||
return sendAttachment(listOf(file, thumbnailFile)) {
|
||||
innerTimeline.sendImage(file.path, thumbnailFile.path, imageInfo.map(), progressCallback?.toProgressWatcher())
|
||||
|
||||
@@ -111,6 +111,7 @@ class FakeMatrixRoom(
|
||||
private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io")
|
||||
private var getWidgetDriverResult: Result<MatrixWidgetDriver> = Result.success(FakeWidgetDriver())
|
||||
private var canUserTriggerRoomNotificationResult: Result<Boolean> = Result.success(true)
|
||||
private var canUserJoinCallResult: Result<Boolean> = Result.success(true)
|
||||
var sendMessageMentions = emptyList<Mention>()
|
||||
val editMessageCalls = mutableListOf<Pair<String, String?>>()
|
||||
|
||||
@@ -292,6 +293,10 @@ class FakeMatrixRoom(
|
||||
return canUserTriggerRoomNotificationResult
|
||||
}
|
||||
|
||||
override suspend fun canUserJoinCall(userId: UserId): Result<Boolean> {
|
||||
return canUserJoinCallResult
|
||||
}
|
||||
|
||||
override suspend fun sendImage(
|
||||
file: File,
|
||||
thumbnailFile: File,
|
||||
@@ -474,6 +479,10 @@ class FakeMatrixRoom(
|
||||
canUserTriggerRoomNotificationResult = result
|
||||
}
|
||||
|
||||
fun givenCanUserJoinCall(result: Result<Boolean>) {
|
||||
canUserJoinCallResult = result
|
||||
}
|
||||
|
||||
fun givenIgnoreResult(result: Result<Unit>) {
|
||||
ignoreResult = result
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user