Browse Source
* Change a room's permissions power levels * Make `currentPermissions` use a `MatrixRoomPowerLevels?` instance instead. * Update strings * Update screenshots --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>pull/2538/head
Jorge Martin Espinosa
6 months ago
committed by
GitHub
71 changed files with 1556 additions and 58 deletions
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
Change a room's permissions power levels. |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions |
||||
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember |
||||
|
||||
interface ChangeRoomPermissionsEvent { |
||||
data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: RoomMember.Role) : ChangeRoomPermissionsEvent |
||||
data object Save : ChangeRoomPermissionsEvent |
||||
data object Exit : ChangeRoomPermissionsEvent |
||||
data object ResetPendingActions : ChangeRoomPermissionsEvent |
||||
} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions |
||||
|
||||
import android.os.Parcelable |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Modifier |
||||
import com.bumble.appyx.core.modality.BuildContext |
||||
import com.bumble.appyx.core.node.Node |
||||
import com.bumble.appyx.core.plugin.Plugin |
||||
import dagger.assisted.Assisted |
||||
import dagger.assisted.AssistedInject |
||||
import io.element.android.anvilannotations.ContributesNode |
||||
import io.element.android.libraries.architecture.NodeInputs |
||||
import io.element.android.libraries.architecture.inputs |
||||
import io.element.android.libraries.di.RoomScope |
||||
import kotlinx.parcelize.Parcelize |
||||
|
||||
@ContributesNode(RoomScope::class) |
||||
class ChangeRoomPermissionsNode @AssistedInject constructor( |
||||
@Assisted buildContext: BuildContext, |
||||
@Assisted plugins: List<Plugin>, |
||||
presenterFactory: ChangeRoomPermissionsPresenter.Factory, |
||||
) : Node(buildContext, plugins = plugins) { |
||||
@Parcelize |
||||
data class Inputs( |
||||
val section: ChangeRoomPermissionsSection, |
||||
) : NodeInputs, Parcelable |
||||
|
||||
private val inputs: Inputs = inputs() |
||||
|
||||
private val presenter = presenterFactory.run { |
||||
create(inputs.section) |
||||
} |
||||
|
||||
@Composable |
||||
override fun View(modifier: Modifier) { |
||||
val state = presenter.present() |
||||
ChangeRoomPermissionsView( |
||||
modifier = modifier, |
||||
state = state, |
||||
onBackPressed = this::navigateUp, |
||||
) |
||||
} |
||||
} |
||||
|
||||
@Parcelize |
||||
enum class ChangeRoomPermissionsSection : Parcelable { |
||||
RoomDetails, |
||||
MessagesAndContent, |
||||
MembershipModeration, |
||||
} |
@ -0,0 +1,145 @@
@@ -0,0 +1,145 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.LaunchedEffect |
||||
import androidx.compose.runtime.derivedStateOf |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.rememberCoroutineScope |
||||
import androidx.compose.runtime.setValue |
||||
import dagger.assisted.Assisted |
||||
import dagger.assisted.AssistedFactory |
||||
import dagger.assisted.AssistedInject |
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.architecture.Presenter |
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom |
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels |
||||
import kotlinx.collections.immutable.ImmutableList |
||||
import kotlinx.collections.immutable.persistentListOf |
||||
import kotlinx.coroutines.CoroutineScope |
||||
import kotlinx.coroutines.launch |
||||
|
||||
class ChangeRoomPermissionsPresenter @AssistedInject constructor( |
||||
@Assisted private val section: ChangeRoomPermissionsSection, |
||||
private val room: MatrixRoom, |
||||
) : Presenter<ChangeRoomPermissionsState> { |
||||
companion object { |
||||
internal fun itemsForSection(section: ChangeRoomPermissionsSection) = when (section) { |
||||
ChangeRoomPermissionsSection.RoomDetails -> persistentListOf( |
||||
RoomPermissionType.ROOM_NAME, |
||||
RoomPermissionType.ROOM_AVATAR, |
||||
RoomPermissionType.ROOM_TOPIC, |
||||
) |
||||
ChangeRoomPermissionsSection.MessagesAndContent -> persistentListOf( |
||||
RoomPermissionType.SEND_EVENTS, |
||||
RoomPermissionType.REDACT_EVENTS, |
||||
) |
||||
ChangeRoomPermissionsSection.MembershipModeration -> persistentListOf( |
||||
RoomPermissionType.INVITE, |
||||
RoomPermissionType.KICK, |
||||
RoomPermissionType.BAN, |
||||
) |
||||
} |
||||
} |
||||
@AssistedFactory |
||||
interface Factory { |
||||
fun create(section: ChangeRoomPermissionsSection): ChangeRoomPermissionsPresenter |
||||
} |
||||
|
||||
private val items: ImmutableList<RoomPermissionType> = itemsForSection(section) |
||||
|
||||
private var initialPermissions by mutableStateOf<MatrixRoomPowerLevels?>(null) |
||||
private var currentPermissions by mutableStateOf<MatrixRoomPowerLevels?>(null) |
||||
private var saveAction by mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) |
||||
private var confirmExitAction by mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) |
||||
|
||||
@Composable |
||||
override fun present(): ChangeRoomPermissionsState { |
||||
val coroutineScope = rememberCoroutineScope() |
||||
|
||||
LaunchedEffect(Unit) { |
||||
updatePermissions() |
||||
} |
||||
|
||||
val hasChanges by remember { |
||||
derivedStateOf { initialPermissions != currentPermissions } |
||||
} |
||||
|
||||
fun handleEvent(event: ChangeRoomPermissionsEvent) { |
||||
when (event) { |
||||
is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> { |
||||
currentPermissions = when (event.action) { |
||||
RoomPermissionType.BAN -> currentPermissions?.copy(ban = event.role.powerLevel) |
||||
RoomPermissionType.INVITE -> currentPermissions?.copy(invite = event.role.powerLevel) |
||||
RoomPermissionType.KICK -> currentPermissions?.copy(kick = event.role.powerLevel) |
||||
RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = event.role.powerLevel) |
||||
RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = event.role.powerLevel) |
||||
RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = event.role.powerLevel) |
||||
RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = event.role.powerLevel) |
||||
RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = event.role.powerLevel) |
||||
} |
||||
} |
||||
is ChangeRoomPermissionsEvent.Save -> coroutineScope.save() |
||||
is ChangeRoomPermissionsEvent.Exit -> { |
||||
confirmExitAction = if (!hasChanges || confirmExitAction.isConfirming()) { |
||||
AsyncAction.Success(Unit) |
||||
} else { |
||||
AsyncAction.Confirming |
||||
} |
||||
} |
||||
is ChangeRoomPermissionsEvent.ResetPendingActions -> { |
||||
saveAction = AsyncAction.Uninitialized |
||||
confirmExitAction = AsyncAction.Uninitialized |
||||
} |
||||
} |
||||
} |
||||
return ChangeRoomPermissionsState( |
||||
section = section, |
||||
currentPermissions = currentPermissions, |
||||
items = items, |
||||
hasChanges = hasChanges, |
||||
saveAction = saveAction, |
||||
confirmExitAction = confirmExitAction, |
||||
eventSink = { handleEvent(it) } |
||||
) |
||||
} |
||||
|
||||
private suspend fun updatePermissions() { |
||||
val powerLevels = room.powerLevels().getOrNull() ?: return |
||||
initialPermissions = powerLevels |
||||
currentPermissions = initialPermissions |
||||
} |
||||
|
||||
private fun CoroutineScope.save() = launch { |
||||
saveAction = AsyncAction.Loading |
||||
val updatedRoomPowerLevels = currentPermissions ?: run { |
||||
saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")) |
||||
return@launch |
||||
} |
||||
room.updatePowerLevels(updatedRoomPowerLevels) |
||||
.onSuccess { |
||||
initialPermissions = currentPermissions |
||||
saveAction = AsyncAction.Success(Unit) |
||||
} |
||||
.onFailure { |
||||
saveAction = AsyncAction.Failure(it) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions |
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels |
||||
import kotlinx.collections.immutable.ImmutableList |
||||
|
||||
data class ChangeRoomPermissionsState( |
||||
val section: ChangeRoomPermissionsSection, |
||||
val currentPermissions: MatrixRoomPowerLevels?, |
||||
val items: ImmutableList<RoomPermissionType>, |
||||
val hasChanges: Boolean, |
||||
val saveAction: AsyncAction<Unit>, |
||||
val confirmExitAction: AsyncAction<Unit>, |
||||
val eventSink: (ChangeRoomPermissionsEvent) -> Unit, |
||||
) |
||||
|
||||
enum class RoomPermissionType { |
||||
BAN, |
||||
INVITE, |
||||
KICK, |
||||
SEND_EVENTS, |
||||
REDACT_EVENTS, |
||||
ROOM_NAME, |
||||
ROOM_AVATAR, |
||||
ROOM_TOPIC |
||||
} |
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions |
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider |
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.matrix.api.room.RoomMember |
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels |
||||
import kotlinx.collections.immutable.toPersistentList |
||||
|
||||
class ChangeRoomPermissionsStatePreviewProvider : PreviewParameterProvider<ChangeRoomPermissionsState> { |
||||
override val values: Sequence<ChangeRoomPermissionsState> |
||||
get() = sequenceOf( |
||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails), |
||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MessagesAndContent), |
||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MembershipModeration), |
||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true), |
||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Loading), |
||||
aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
hasChanges = true, |
||||
saveAction = AsyncAction.Failure(IllegalStateException("Failed to save changes")) |
||||
), |
||||
aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, confirmExitAction = AsyncAction.Confirming), |
||||
) |
||||
} |
||||
|
||||
internal fun aChangeRoomPermissionsState( |
||||
section: ChangeRoomPermissionsSection, |
||||
currentPermissions: MatrixRoomPowerLevels = previewPermissions(), |
||||
items: List<RoomPermissionType> = ChangeRoomPermissionsPresenter.itemsForSection(section), |
||||
hasChanges: Boolean = false, |
||||
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized, |
||||
confirmExitAction: AsyncAction<Unit> = AsyncAction.Uninitialized, |
||||
eventSink: (ChangeRoomPermissionsEvent) -> Unit = {}, |
||||
) = ChangeRoomPermissionsState( |
||||
section = section, |
||||
currentPermissions = currentPermissions, |
||||
items = items.toPersistentList(), |
||||
hasChanges = hasChanges, |
||||
saveAction = saveAction, |
||||
confirmExitAction = confirmExitAction, |
||||
eventSink = eventSink, |
||||
) |
||||
|
||||
private fun previewPermissions(): MatrixRoomPowerLevels { |
||||
return MatrixRoomPowerLevels( |
||||
// MembershipModeration section |
||||
invite = RoomMember.Role.ADMIN.powerLevel, |
||||
kick = RoomMember.Role.MODERATOR.powerLevel, |
||||
ban = RoomMember.Role.USER.powerLevel, |
||||
// MessagesAndContent section |
||||
redactEvents = RoomMember.Role.MODERATOR.powerLevel, |
||||
sendEvents = RoomMember.Role.ADMIN.powerLevel, |
||||
// RoomDetails section |
||||
roomName = RoomMember.Role.ADMIN.powerLevel, |
||||
roomAvatar = RoomMember.Role.MODERATOR.powerLevel, |
||||
roomTopic = RoomMember.Role.USER.powerLevel, |
||||
) |
||||
} |
@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions |
||||
|
||||
import androidx.activity.compose.BackHandler |
||||
import androidx.compose.foundation.layout.Column |
||||
import androidx.compose.foundation.layout.padding |
||||
import androidx.compose.material3.ExperimentalMaterial3Api |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.res.stringResource |
||||
import androidx.compose.ui.tooling.preview.PreviewParameter |
||||
import io.element.android.compound.theme.ElementTheme |
||||
import io.element.android.compound.tokens.generated.CompoundIcons |
||||
import io.element.android.features.roomdetails.impl.R |
||||
import io.element.android.libraries.core.bool.orFalse |
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView |
||||
import io.element.android.libraries.designsystem.components.button.BackButton |
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog |
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent |
||||
import io.element.android.libraries.designsystem.preview.ElementPreview |
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight |
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle |
||||
import io.element.android.libraries.designsystem.theme.components.IconSource |
||||
import io.element.android.libraries.designsystem.theme.components.ListItem |
||||
import io.element.android.libraries.designsystem.theme.components.ListItemStyle |
||||
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader |
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold |
||||
import io.element.android.libraries.designsystem.theme.components.Text |
||||
import io.element.android.libraries.designsystem.theme.components.TextButton |
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar |
||||
import io.element.android.libraries.matrix.api.room.RoomMember |
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels |
||||
import io.element.android.libraries.ui.strings.CommonStrings |
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class) |
||||
@Composable |
||||
fun ChangeRoomPermissionsView( |
||||
state: ChangeRoomPermissionsState, |
||||
onBackPressed: () -> Unit, |
||||
modifier: Modifier = Modifier, |
||||
) { |
||||
BackHandler { |
||||
state.eventSink(ChangeRoomPermissionsEvent.Exit) |
||||
} |
||||
Scaffold( |
||||
modifier = modifier, |
||||
topBar = { |
||||
val title = when (state.section) { |
||||
ChangeRoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details) |
||||
ChangeRoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_change_permissions_messages_and_content) |
||||
ChangeRoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_change_permissions_member_moderation) |
||||
} |
||||
TopAppBar( |
||||
title = { Text(text = title, style = ElementTheme.typography.aliasScreenTitle) }, |
||||
navigationIcon = { |
||||
BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }) |
||||
}, |
||||
actions = { |
||||
TextButton( |
||||
text = stringResource(CommonStrings.action_save), |
||||
onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, |
||||
enabled = state.hasChanges, |
||||
) |
||||
} |
||||
) |
||||
} |
||||
) { padding -> |
||||
Column(modifier = Modifier.padding(padding)) { |
||||
for ((index, permissionItem) in state.items.withIndex()) { |
||||
ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0) |
||||
SelectRoleItem( |
||||
permissionsItem = permissionItem, |
||||
role = RoomMember.Role.ADMIN, |
||||
currentPermissions = state.currentPermissions |
||||
) { item, role -> |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) |
||||
} |
||||
SelectRoleItem( |
||||
permissionsItem = permissionItem, |
||||
role = RoomMember.Role.MODERATOR, |
||||
currentPermissions = state.currentPermissions |
||||
) { item, role -> |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) |
||||
} |
||||
SelectRoleItem( |
||||
permissionsItem = permissionItem, |
||||
role = RoomMember.Role.USER, |
||||
currentPermissions = state.currentPermissions |
||||
) { item, role -> |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
AsyncActionView( |
||||
async = state.saveAction, |
||||
onSuccess = { onBackPressed() }, |
||||
onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } |
||||
) |
||||
|
||||
AsyncActionView( |
||||
async = state.confirmExitAction, |
||||
onSuccess = { onBackPressed() }, |
||||
confirmationDialog = { |
||||
ConfirmationDialog( |
||||
title = stringResource(R.string.screen_room_change_role_unsaved_changes_title), |
||||
content = stringResource(R.string.screen_room_change_role_unsaved_changes_description), |
||||
submitText = stringResource(CommonStrings.action_save), |
||||
cancelText = stringResource(CommonStrings.action_discard), |
||||
onSubmitClicked = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, |
||||
onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) } |
||||
) |
||||
}, |
||||
onErrorDismiss = {}, |
||||
) |
||||
} |
||||
|
||||
@Composable |
||||
private fun SelectRoleItem( |
||||
permissionsItem: RoomPermissionType, |
||||
role: RoomMember.Role, |
||||
currentPermissions: MatrixRoomPowerLevels?, |
||||
onClick: (RoomPermissionType, RoomMember.Role) -> Unit |
||||
) { |
||||
val title = when (role) { |
||||
RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_change_permissions_administrators) |
||||
RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_change_permissions_moderators) |
||||
RoomMember.Role.USER -> stringResource(R.string.screen_room_change_permissions_everyone) |
||||
} |
||||
ListItem( |
||||
headlineContent = { Text(text = title) }, |
||||
trailingContent = if (currentPermissions?.isSelected(permissionsItem, role).orFalse()) { |
||||
ListItemContent.Icon(IconSource.Vector(CompoundIcons.Check())) |
||||
} else { |
||||
null |
||||
}, |
||||
style = ListItemStyle.Primary, |
||||
onClick = { onClick(permissionsItem, role) }, |
||||
) |
||||
} |
||||
|
||||
private fun MatrixRoomPowerLevels.isSelected(item: RoomPermissionType, role: RoomMember.Role): Boolean { |
||||
return when (item) { |
||||
RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(ban) == role |
||||
RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(invite) == role |
||||
RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(kick) == role |
||||
RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(sendEvents) == role |
||||
RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(redactEvents) == role |
||||
RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(roomName) == role |
||||
RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(roomAvatar) == role |
||||
RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(roomTopic) == role |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
private fun titleForSection(item: RoomPermissionType): String = when (item) { |
||||
RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people) |
||||
RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people) |
||||
RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people) |
||||
RoomPermissionType.SEND_EVENTS -> stringResource(R.string.screen_room_change_permissions_send_messages) |
||||
RoomPermissionType.REDACT_EVENTS -> stringResource(R.string.screen_room_change_permissions_delete_messages) |
||||
RoomPermissionType.ROOM_NAME -> stringResource(R.string.screen_room_change_permissions_room_name) |
||||
RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar) |
||||
RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic) |
||||
} |
||||
|
||||
@PreviewsDayNight |
||||
@Composable |
||||
internal fun ChangeRoomPermissionsViewPreview(@PreviewParameter(ChangeRoomPermissionsStatePreviewProvider::class) state: ChangeRoomPermissionsState) { |
||||
ElementPreview { |
||||
ChangeRoomPermissionsView( |
||||
state = state, |
||||
onBackPressed = {}, |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,294 @@
@@ -0,0 +1,294 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.rolesandpermissions.permissions |
||||
|
||||
import app.cash.molecule.RecompositionMode |
||||
import app.cash.molecule.moleculeFlow |
||||
import app.cash.turbine.Event |
||||
import app.cash.turbine.TurbineTestContext |
||||
import app.cash.turbine.test |
||||
import com.google.common.truth.Truth.assertThat |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsEvent |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsPresenter |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsSection |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsState |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.RoomPermissionType |
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.ADMIN |
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.MODERATOR |
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels |
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom |
||||
import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevels |
||||
import kotlinx.coroutines.test.runTest |
||||
import org.junit.Test |
||||
|
||||
class ChangeRoomPermissionsPresenterTests { |
||||
@Test |
||||
fun `present - initial state`() = runTest { |
||||
val section = ChangeRoomPermissionsSection.RoomDetails |
||||
val presenter = createChangeRoomPermissionsPresenter(section = section) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
// Initial state, no permissions loaded |
||||
awaitItem().run { |
||||
assertThat(this.section).isEqualTo(section) |
||||
assertThat(this.currentPermissions).isNull() |
||||
assertThat(this.items).isNotEmpty() |
||||
assertThat(this.hasChanges).isFalse() |
||||
assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized) |
||||
assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized) |
||||
} |
||||
|
||||
// Updated state, permissions loaded |
||||
assertThat(awaitItem().currentPermissions).isEqualTo(defaultPermissions()) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - RoomDetails section contains the right items`() = runTest { |
||||
val section = ChangeRoomPermissionsSection.RoomDetails |
||||
val presenter = createChangeRoomPermissionsPresenter(section = section) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
assertThat(awaitUpdatedItem().items).containsExactly( |
||||
RoomPermissionType.ROOM_NAME, |
||||
RoomPermissionType.ROOM_AVATAR, |
||||
RoomPermissionType.ROOM_TOPIC, |
||||
) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - MessagesAndContent section contains the right items`() = runTest { |
||||
val section = ChangeRoomPermissionsSection.MessagesAndContent |
||||
val presenter = createChangeRoomPermissionsPresenter(section = section) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
assertThat(awaitUpdatedItem().items).containsExactly( |
||||
RoomPermissionType.SEND_EVENTS, |
||||
RoomPermissionType.REDACT_EVENTS, |
||||
) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - MembershipModeration section contains the right items`() = runTest { |
||||
val section = ChangeRoomPermissionsSection.MembershipModeration |
||||
val presenter = createChangeRoomPermissionsPresenter(section = section) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
assertThat(awaitUpdatedItem().items).containsExactly( |
||||
RoomPermissionType.INVITE, |
||||
RoomPermissionType.KICK, |
||||
RoomPermissionType.BAN, |
||||
) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest { |
||||
val presenter = createChangeRoomPermissionsPresenter() |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val state = awaitUpdatedItem() |
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) |
||||
assertThat(state.hasChanges).isFalse() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) |
||||
|
||||
awaitItem().run { |
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) |
||||
assertThat(hasChanges).isTrue() |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - ChangeMinimumRoleForAction works for all actions`() = runTest { |
||||
val presenter = createChangeRoomPermissionsPresenter() |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val state = awaitUpdatedItem() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, MODERATOR)) |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, MODERATOR)) |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, MODERATOR)) |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, MODERATOR)) |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, MODERATOR)) |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, MODERATOR)) |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, MODERATOR)) |
||||
|
||||
val items = cancelAndConsumeRemainingEvents() |
||||
|
||||
(items.last() as? Event.Item<ChangeRoomPermissionsState>)?.value?.run { |
||||
assertThat(currentPermissions).isEqualTo( |
||||
MatrixRoomPowerLevels( |
||||
invite = MODERATOR.powerLevel, |
||||
kick = MODERATOR.powerLevel, |
||||
ban = MODERATOR.powerLevel, |
||||
redactEvents = MODERATOR.powerLevel, |
||||
sendEvents = MODERATOR.powerLevel, |
||||
roomName = MODERATOR.powerLevel, |
||||
roomAvatar = MODERATOR.powerLevel, |
||||
roomTopic = MODERATOR.powerLevel, |
||||
) |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - Save updates the current permissions and resets hasChanges`() = runTest { |
||||
val presenter = createChangeRoomPermissionsPresenter() |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val state = awaitUpdatedItem() |
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) |
||||
assertThat(state.hasChanges).isFalse() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) |
||||
assertThat(awaitItem().hasChanges).isTrue() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Save) |
||||
|
||||
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading) |
||||
assertThat(awaitItem().hasChanges).isFalse() |
||||
awaitItem().run { |
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) |
||||
assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - Save will fail if there are not current permissions`() = runTest { |
||||
val room = FakeMatrixRoom().apply { |
||||
givenPowerLevelsResult(Result.failure(IllegalStateException("Failed to load power levels"))) |
||||
} |
||||
val presenter = createChangeRoomPermissionsPresenter(room = room) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val state = awaitItem() |
||||
assertThat(state.currentPermissions).isNull() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Save) |
||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - Save can handle failures and they can be cleared`() = runTest { |
||||
val room = FakeMatrixRoom().apply { |
||||
givenUpdatePowerLevelsResult(Result.failure(IllegalStateException("Failed to update power levels"))) |
||||
} |
||||
val presenter = createChangeRoomPermissionsPresenter(room = room) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val state = awaitUpdatedItem() |
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel) |
||||
assertThat(state.hasChanges).isFalse() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) |
||||
assertThat(awaitItem().hasChanges).isTrue() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Save) |
||||
|
||||
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading) |
||||
awaitItem().run { |
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) |
||||
// Couldn't save the changes, so they're still pending |
||||
assertThat(hasChanges).isTrue() |
||||
assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) |
||||
} |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) |
||||
awaitItem().run { |
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel) |
||||
assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) |
||||
assertThat(hasChanges).isTrue() |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - Exit does not need a confirmation when there are no pending changes`() = runTest { |
||||
val presenter = createChangeRoomPermissionsPresenter() |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val state = awaitUpdatedItem() |
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR)) |
||||
assertThat(awaitItem().hasChanges).isTrue() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Exit) |
||||
assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Confirming) |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Exit) |
||||
assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - Exit needs confirmation when there are pending changes`() = runTest { |
||||
val presenter = createChangeRoomPermissionsPresenter() |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
val state = awaitUpdatedItem() |
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Exit) |
||||
|
||||
assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) |
||||
} |
||||
} |
||||
|
||||
private fun createChangeRoomPermissionsPresenter( |
||||
section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails, |
||||
room: FakeMatrixRoom = FakeMatrixRoom(), |
||||
) = ChangeRoomPermissionsPresenter( |
||||
section = section, |
||||
room = room, |
||||
) |
||||
|
||||
private fun defaultPermissions() = defaultRoomPowerLevels().run { |
||||
MatrixRoomPowerLevels( |
||||
invite = invite, |
||||
kick = kick, |
||||
ban = ban, |
||||
redactEvents = redactEvents, |
||||
sendEvents = sendEvents, |
||||
roomName = roomName, |
||||
roomAvatar = roomAvatar, |
||||
roomTopic = roomTopic, |
||||
) |
||||
} |
||||
|
||||
private suspend fun TurbineTestContext<ChangeRoomPermissionsState>.awaitUpdatedItem(): ChangeRoomPermissionsState { |
||||
skipItems(1) |
||||
return awaitItem() |
||||
} |
||||
} |
@ -0,0 +1,201 @@
@@ -0,0 +1,201 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.features.roomdetails.rolesandpermissions.permissions |
||||
|
||||
import androidx.activity.ComponentActivity |
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule |
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule |
||||
import androidx.compose.ui.test.onAllNodesWithText |
||||
import androidx.compose.ui.test.onFirst |
||||
import androidx.compose.ui.test.performClick |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import io.element.android.features.roomdetails.impl.R |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsEvent |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsSection |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsState |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsView |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.RoomPermissionType |
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.aChangeRoomPermissionsState |
||||
import io.element.android.libraries.architecture.AsyncAction |
||||
import io.element.android.libraries.matrix.api.room.RoomMember |
||||
import io.element.android.libraries.ui.strings.CommonStrings |
||||
import io.element.android.tests.testutils.EnsureNeverCalled |
||||
import io.element.android.tests.testutils.EventsRecorder |
||||
import io.element.android.tests.testutils.clickOn |
||||
import io.element.android.tests.testutils.clickOnFirst |
||||
import io.element.android.tests.testutils.ensureCalledOnce |
||||
import io.element.android.tests.testutils.pressBack |
||||
import io.element.android.tests.testutils.pressBackKey |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
import org.junit.rules.TestRule |
||||
import org.junit.runner.RunWith |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class ChangeRoomPermissionsViewTests { |
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>() |
||||
|
||||
@Test |
||||
fun `click on back icon invokes Exit`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
eventsRecorder = recorder, |
||||
) |
||||
rule.pressBack() |
||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) |
||||
} |
||||
|
||||
@Test |
||||
fun `click on back key invokes Exit`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
eventsRecorder = recorder, |
||||
) |
||||
rule.pressBackKey() |
||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) |
||||
} |
||||
|
||||
@Test |
||||
fun `when confirming exit with pending changes, using the back key actually exits`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
state = aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
hasChanges = true, |
||||
eventSink = recorder, |
||||
), |
||||
eventsRecorder = recorder, |
||||
) |
||||
rule.pressBackKey() |
||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) |
||||
} |
||||
|
||||
@Test |
||||
fun `when confirming exit with pending changes, clicking on 'discard' button in the dialog actually exits`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
state = aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
hasChanges = true, |
||||
confirmExitAction = AsyncAction.Confirming, |
||||
eventSink = recorder, |
||||
), |
||||
eventsRecorder = recorder, |
||||
) |
||||
rule.clickOn(CommonStrings.action_discard) |
||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) |
||||
} |
||||
|
||||
@Test |
||||
fun `when confirming exit with pending changes, clicking on 'save' button in the dialog saves the changes`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
state = aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
hasChanges = true, |
||||
confirmExitAction = AsyncAction.Confirming, |
||||
eventSink = recorder, |
||||
), |
||||
eventsRecorder = recorder, |
||||
) |
||||
rule.clickOnFirst(CommonStrings.action_save) |
||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Save) |
||||
} |
||||
|
||||
@Test |
||||
fun `click on a role item triggers ChangeRole event`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
eventsRecorder = recorder, |
||||
) |
||||
val admins = rule.activity.getText(R.string.screen_room_change_permissions_administrators).toString() |
||||
val moderators = rule.activity.getText(R.string.screen_room_change_permissions_moderators).toString() |
||||
val users = rule.activity.getText(R.string.screen_room_change_permissions_everyone).toString() |
||||
rule.onAllNodesWithText(admins).onFirst().performClick() |
||||
rule.onAllNodesWithText(moderators).onFirst().performClick() |
||||
rule.onAllNodesWithText(users).onFirst().performClick() |
||||
recorder.assertList( |
||||
listOf( |
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.ADMIN), |
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.MODERATOR), |
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.USER), |
||||
) |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun `click on the Save menu item triggers Save event`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
state = aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
hasChanges = true, |
||||
eventSink = recorder, |
||||
), |
||||
eventsRecorder = recorder, |
||||
) |
||||
rule.clickOn(CommonStrings.action_save) |
||||
recorder.assertSingle(ChangeRoomPermissionsEvent.Save) |
||||
} |
||||
|
||||
@Test |
||||
fun `a successful save exits the screen`() { |
||||
ensureCalledOnce { callback -> |
||||
rule.setChangeRoomPermissionsRule( |
||||
state = aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
hasChanges = true, |
||||
saveAction = AsyncAction.Success(Unit), |
||||
), |
||||
onBackPressed = callback |
||||
) |
||||
rule.clickOn(CommonStrings.action_save) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `click on the Ok option in save error dialog triggers ResetPendingAction event`() { |
||||
val recorder = EventsRecorder<ChangeRoomPermissionsEvent>() |
||||
rule.setChangeRoomPermissionsRule( |
||||
state = aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
hasChanges = true, |
||||
saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")), |
||||
eventSink = recorder, |
||||
), |
||||
eventsRecorder = recorder, |
||||
) |
||||
rule.clickOn(CommonStrings.action_ok) |
||||
recorder.assertSingle(ChangeRoomPermissionsEvent.ResetPendingActions) |
||||
} |
||||
} |
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRoomPermissionsRule( |
||||
eventsRecorder: EventsRecorder<ChangeRoomPermissionsEvent> = EventsRecorder(expectEvents = false), |
||||
state: ChangeRoomPermissionsState = aChangeRoomPermissionsState( |
||||
section = ChangeRoomPermissionsSection.RoomDetails, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
onBackPressed: () -> Unit = EnsureNeverCalled(), |
||||
) { |
||||
setContent { |
||||
ChangeRoomPermissionsView( |
||||
state = state, |
||||
onBackPressed = onBackPressed, |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
/* |
||||
* Copyright (c) 2024 New Vector Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package io.element.android.libraries.matrix.impl.room.powerlevels |
||||
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels |
||||
import org.matrix.rustcomponents.sdk.RoomPowerLevels as RustRoomPowerLevels |
||||
|
||||
object RoomPowerLevelsMapper { |
||||
fun map(roomPowerLevels: RustRoomPowerLevels): MatrixRoomPowerLevels { |
||||
return MatrixRoomPowerLevels( |
||||
ban = roomPowerLevels.ban, |
||||
invite = roomPowerLevels.invite, |
||||
kick = roomPowerLevels.kick, |
||||
sendEvents = roomPowerLevels.eventsDefault, |
||||
redactEvents = roomPowerLevels.redact, |
||||
roomName = roomPowerLevels.roomName, |
||||
roomAvatar = roomPowerLevels.roomAvatar, |
||||
roomTopic = roomPowerLevels.roomTopic |
||||
) |
||||
} |
||||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_2,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_2,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Day-8_9_null_2,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_2,NEXUS_5,1.0,en].png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_2,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_2,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RoomDetailsAdminSettingsView_null_RoomDetailsAdminSettingsView-Night-8_10_null_2,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_2,NEXUS_5,1.0,en].png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue