Creators can downgrade owners. Fixes #5632
This commit is contained in:
parent
f288803189
commit
fdc133ea77
4 changed files with 46 additions and 44 deletions
|
|
@ -44,6 +44,8 @@ import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
@ -77,7 +79,23 @@ class ChangeRolesPresenter(
|
||||||
}
|
}
|
||||||
val saveState: MutableState<AsyncAction<Boolean>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val saveState: MutableState<AsyncAction<Boolean>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val usersWithRole = produceState<ImmutableList<MatrixUser>>(initialValue = persistentListOf()) {
|
val usersWithRole = produceState<ImmutableList<MatrixUser>>(initialValue = persistentListOf()) {
|
||||||
room.usersWithRole(role).map { members -> members.map { it.toMatrixUser() } }
|
// If the role is admin, we need to include the owners as well since they implicitly have admin role
|
||||||
|
val owners = if (role == RoomMember.Role.Admin) {
|
||||||
|
combine(
|
||||||
|
room.usersWithRole(RoomMember.Role.Owner(isCreator = true)),
|
||||||
|
room.usersWithRole(RoomMember.Role.Owner(isCreator = false)),
|
||||||
|
) { creators, superAdmins ->
|
||||||
|
creators + superAdmins
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyFlow()
|
||||||
|
}
|
||||||
|
combine(
|
||||||
|
owners,
|
||||||
|
room.usersWithRole(role),
|
||||||
|
) { owners, users ->
|
||||||
|
owners + users
|
||||||
|
}.map { members -> members.map { it.toMatrixUser() } }
|
||||||
.onEach { users ->
|
.onEach { users ->
|
||||||
val previous = value
|
val previous = value
|
||||||
value = users.toImmutableList()
|
value = users.toImmutableList()
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider<ChangeRolesState> {
|
||||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading),
|
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading),
|
||||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(true)),
|
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(true)),
|
||||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))),
|
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))),
|
||||||
aChangeRolesStateWithOwners(
|
aChangeRolesStateWithOwners(role = RoomMember.Role.Admin),
|
||||||
role = RoomMember.Role.Admin,
|
|
||||||
// Do not include the owners in the selectedUsers (the presenter will not do it), the View will add them
|
|
||||||
selectedUsers = listOf(
|
|
||||||
aMatrixUser(id = "@carol:server.org", displayName = "Carol"),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false)),
|
aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false)),
|
||||||
aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false))
|
aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false))
|
||||||
.copy(savingState = ConfirmingModifyingOwners),
|
.copy(savingState = ConfirmingModifyingOwners),
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -72,7 +71,6 @@ import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
|
||||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -222,18 +220,7 @@ private fun SearchResultsList(
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
val usersInHorizontalRow = remember(searchResults.owners, selectedUsers) {
|
selectedUsersList(selectedUsers)
|
||||||
if (currentRole == RoomMember.Role.Admin) {
|
|
||||||
// Also include the owners in the horizontal list
|
|
||||||
val owners = searchResults.owners.map {
|
|
||||||
it.toMatrixUser()
|
|
||||||
}
|
|
||||||
(owners + selectedUsers).toImmutableList()
|
|
||||||
} else {
|
|
||||||
selectedUsers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedUsersList(usersInHorizontalRow)
|
|
||||||
}
|
}
|
||||||
if (searchResults.owners.isNotEmpty()) {
|
if (searchResults.owners.isNotEmpty()) {
|
||||||
stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_owners)) }
|
stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_owners)) }
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ class ChangeRolesPresenterTest {
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(canChangeMemberRole(A_USER_ID_2)).isTrue() // Admin
|
assertThat(canChangeMemberRole(A_USER_ID_2)).isTrue() // Admin
|
||||||
assertThat(canChangeMemberRole(A_USER_ID_3)).isTrue() // Moderator
|
assertThat(canChangeMemberRole(A_USER_ID_3)).isTrue() // Moderator
|
||||||
|
assertThat(canChangeMemberRole(superAdminUserId)).isTrue() // Super admin
|
||||||
assertThat(canChangeMemberRole(creatorUserId)).isFalse() // Owner
|
assertThat(canChangeMemberRole(creatorUserId)).isFalse() // Owner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +171,7 @@ class ChangeRolesPresenterTest {
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(room = room)
|
val presenter = createChangeRolesPresenter(room = room)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(2)
|
||||||
awaitItem().searchResults.run {
|
awaitItem().searchResults.run {
|
||||||
assertThat(this).isInstanceOf(SearchBarResultState.Results::class.java)
|
assertThat(this).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||||
val results = (this as SearchBarResultState.Results).results
|
val results = (this as SearchBarResultState.Results).results
|
||||||
|
|
@ -424,11 +425,14 @@ class ChangeRolesPresenterTest {
|
||||||
analyticsService = analyticsService
|
analyticsService = analyticsService
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(2)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).isEmpty()
|
||||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
awaitItem().also {
|
||||||
|
assertThat(it.selectedUsers).hasSize(1)
|
||||||
|
it.eventSink(ChangeRolesEvent.Save)
|
||||||
|
}
|
||||||
assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
||||||
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.Moderator))
|
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.Moderator))
|
||||||
|
|
@ -459,14 +463,13 @@ class ChangeRolesPresenterTest {
|
||||||
analyticsService = analyticsService
|
analyticsService = analyticsService
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).isEmpty()
|
||||||
|
|
||||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||||
|
awaitItem().also {
|
||||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
assertThat(it.selectedUsers).hasSize(1)
|
||||||
|
it.eventSink(ChangeRolesEvent.Save)
|
||||||
|
}
|
||||||
assertThat(awaitItem().savingState.isConfirming()).isTrue()
|
assertThat(awaitItem().savingState.isConfirming()).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -494,12 +497,12 @@ class ChangeRolesPresenterTest {
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).hasSize(2)
|
||||||
|
|
||||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||||
|
awaitItem().also {
|
||||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
assertThat(it.selectedUsers).hasSize(1)
|
||||||
|
it.eventSink(ChangeRolesEvent.Save)
|
||||||
|
}
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true))
|
||||||
|
|
@ -517,25 +520,25 @@ class ChangeRolesPresenterTest {
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Moderator, room = room)
|
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Moderator, room = room)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(2)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.selectedUsers).hasSize(1)
|
assertThat(initialState.selectedUsers).isEmpty()
|
||||||
|
|
||||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||||
|
awaitItem().also {
|
||||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
assertThat(it.selectedUsers).hasSize(1)
|
||||||
|
it.eventSink(ChangeRolesEvent.Save)
|
||||||
|
}
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val failedState = awaitItem()
|
val failedState = awaitItem()
|
||||||
assertThat(failedState.savingState).isInstanceOf(AsyncAction.Failure::class.java)
|
assertThat(failedState.savingState).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
|
|
||||||
failedState.eventSink(ChangeRolesEvent.CloseDialog)
|
failedState.eventSink(ChangeRolesEvent.CloseDialog)
|
||||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Uninitialized)
|
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test analytics mapping`()= runTest {
|
fun `test analytics mapping`() = runTest {
|
||||||
val presenter = createChangeRolesPresenter()
|
val presenter = createChangeRolesPresenter()
|
||||||
with(presenter) {
|
with(presenter) {
|
||||||
assertThat(RoomMember.Role.User.toAnalyticsMemberRole()).isEqualTo(RoomModeration.Role.User)
|
assertThat(RoomMember.Role.User.toAnalyticsMemberRole()).isEqualTo(RoomModeration.Role.User)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue