change(roles and permissions): improve the flow

This commit is contained in:
ganfra 2025-11-05 14:42:34 +01:00
parent 849dfa4a5e
commit 288171ba90
13 changed files with 93 additions and 85 deletions

View file

@ -40,7 +40,6 @@ import io.element.android.features.logout.api.direct.DirectLogoutView
import io.element.android.features.reportroom.api.ReportRoomEntryPoint import io.element.android.features.reportroom.api.ReportRoomEntryPoint
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.appyx.launchMolecule import io.element.android.libraries.architecture.appyx.launchMolecule
@ -94,10 +93,12 @@ class HomeFlowNode(
changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy, changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy,
-> ->
commonLifecycle.coroutineScope.launch { commonLifecycle.coroutineScope.launch {
changeRoomMemberRolesNode.waitForRoleChanged() val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion()
withContext(NonCancellable) { withContext(NonCancellable) {
backstack.pop() backstack.pop()
onNewOwnersSelected(changeRoomMemberRolesNode.roomId) if(isNewOwnerSelected) {
onNewOwnersSelected(changeRoomMemberRolesNode.roomId)
}
} }
} }
} }

View file

@ -24,11 +24,11 @@ fun interface ChangeRoomMemberRolesEntryPoint : FeatureEntryPoint {
interface NodeProxy { interface NodeProxy {
val roomId: RoomId val roomId: RoomId
suspend fun waitForRoleChanged() suspend fun waitForCompletion(): Boolean
} }
} }
enum class ChangeRoomMemberRolesListType : NodeInputs { enum class ChangeRoomMemberRolesListType {
SelectNewOwnersWhenLeaving, SelectNewOwnersWhenLeaving,
Admins, Admins,
Moderators Moderators

View file

@ -8,8 +8,11 @@
package io.element.android.features.rolesandpermissions.impl package io.element.android.features.rolesandpermissions.impl
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
@ -20,15 +23,18 @@ import com.bumble.appyx.navmodel.backstack.operation.push
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsNode import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsNode
import io.element.android.features.rolesandpermissions.impl.roles.ChangeRolesNode
import io.element.android.features.rolesandpermissions.impl.root.RolesAndPermissionsNode import io.element.android.features.rolesandpermissions.impl.root.RolesAndPermissionsNode
import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorState
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -37,11 +43,9 @@ import kotlinx.parcelize.Parcelize
class RolesAndPermissionsFlowNode( class RolesAndPermissionsFlowNode(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val joinedRoom: JoinedRoom,
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
) : BaseFlowNode<RolesAndPermissionsFlowNode.NavTarget>( ) : BaseFlowNode<RolesAndPermissionsFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.AdminSettings, initialElement = NavTarget.Root,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
buildContext = buildContext, buildContext = buildContext,
@ -49,38 +53,49 @@ class RolesAndPermissionsFlowNode(
) { ) {
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
data object AdminSettings : NavTarget data object Root : NavTarget
@Parcelize @Parcelize
data object AdminList : NavTarget data object ChangeAdmins : NavTarget
@Parcelize @Parcelize
data object ModeratorList : NavTarget data object ChangeModerators : NavTarget
@Parcelize @Parcelize
data object ChangeRoomPermissions: NavTarget data object ChangeRoomPermissions: NavTarget
} }
private val asyncIndicatorState = AsyncIndicatorState()
override fun onBuilt() { override fun onBuilt() {
super.onBuilt() super.onBuilt()
whenChildAttached { lifecycle, node: ChangeRoomMemberRolesEntryPoint.NodeProxy -> whenChildAttached { lifecycle, node: ChangeRolesNode ->
lifecycle.coroutineScope.launch { lifecycle.coroutineScope.launch {
node.waitForRoleChanged() val changesSaved = node.waitForCompletion()
backstack.pop() onChangeComplete(changesSaved)
}
}
}
private fun onChangeComplete(changesSaved: Boolean) {
backstack.pop()
if (changesSaved) {
asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) {
AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes))
} }
} }
} }
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
is NavTarget.AdminSettings -> { is NavTarget.Root -> {
val callback = object : RolesAndPermissionsNode.Callback { val callback = object : RolesAndPermissionsNode.Callback {
override fun openAdminList() { override fun openAdminList() {
backstack.push(NavTarget.AdminList) backstack.push(NavTarget.ChangeAdmins)
} }
override fun openModeratorList() { override fun openModeratorList() {
backstack.push(NavTarget.ModeratorList) backstack.push(NavTarget.ChangeModerators)
} }
override fun openEditPermissions() { override fun openEditPermissions() {
@ -93,30 +108,32 @@ class RolesAndPermissionsFlowNode(
plugins = listOf(callback), plugins = listOf(callback),
) )
} }
is NavTarget.AdminList -> { is NavTarget.ChangeAdmins -> {
changeRoomMemberRolesEntryPoint.createNode( val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Admins)
parentNode = this, createNode<ChangeRolesNode>(buildContext = buildContext, plugins = listOf(inputs))
buildContext = buildContext,
room = joinedRoom,
listType = ChangeRoomMemberRolesListType.Admins,
)
} }
is NavTarget.ModeratorList -> { is NavTarget.ChangeModerators -> {
changeRoomMemberRolesEntryPoint.createNode( val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Moderators)
parentNode = this, createNode<ChangeRolesNode>(buildContext = buildContext, plugins = listOf(inputs))
buildContext = buildContext,
room = joinedRoom,
listType = ChangeRoomMemberRolesListType.Moderators,
)
} }
is NavTarget.ChangeRoomPermissions -> { is NavTarget.ChangeRoomPermissions -> {
createNode<ChangeRoomPermissionsNode>(buildContext = buildContext) val callback = object : ChangeRoomPermissionsNode.Callback {
override fun onComplete(changesSaved: Boolean) {
onChangeComplete(changesSaved)
}
}
createNode<ChangeRoomPermissionsNode>(buildContext = buildContext, plugins = listOf(callback))
} }
} }
} }
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
BackstackView() Box(modifier = modifier) {
BackstackView()
AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState)
}
} }
} }

View file

@ -15,6 +15,7 @@ import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
@ContributesNode(RoomScope::class) @ContributesNode(RoomScope::class)
@ -25,13 +26,19 @@ class ChangeRoomPermissionsNode(
private val presenter: ChangeRoomPermissionsPresenter, private val presenter: ChangeRoomPermissionsPresenter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onComplete(changesSaved: Boolean)
}
private val callback: Callback = callback()
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
ChangeRoomPermissionsView( ChangeRoomPermissionsView(
modifier = modifier, modifier = modifier,
state = state, state = state,
onBackClick = this::navigateUp, onComplete = callback::onComplete,
) )
} }
} }

View file

@ -16,26 +16,17 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.rolesandpermissions.impl.R import io.element.android.features.rolesandpermissions.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.async.AsyncActionView
import io.element.android.libraries.designsystem.components.button.BackButton 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.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
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.ListSectionHeader
import io.element.android.libraries.designsystem.theme.components.Scaffold 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.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar 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.RoomPowerLevelsValues
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -43,7 +34,7 @@ import kotlinx.collections.immutable.toImmutableList
@Composable @Composable
fun ChangeRoomPermissionsView( fun ChangeRoomPermissionsView(
state: ChangeRoomPermissionsState, state: ChangeRoomPermissionsState,
onBackClick: () -> Unit, onComplete: (Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
BackHandler { BackHandler {
@ -74,7 +65,7 @@ fun ChangeRoomPermissionsView(
) { ) {
state.itemsBySection.onEachIndexed { index, (section, items) -> state.itemsBySection.onEachIndexed { index, (section, items) ->
item { item {
ListSectionHeader(titleForSection(section), hasDivider = index>0) ListSectionHeader(titleForSection(section), hasDivider = index > 0)
} }
for (permissionType in items) { for (permissionType in items) {
item { item {
@ -99,13 +90,13 @@ fun ChangeRoomPermissionsView(
AsyncActionView( AsyncActionView(
async = state.saveAction, async = state.saveAction,
onSuccess = { onBackClick() }, onSuccess = { onComplete(true) },
onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) }
) )
AsyncActionView( AsyncActionView(
async = state.confirmExitAction, async = state.confirmExitAction,
onSuccess = { onBackClick() }, onSuccess = { onComplete(false) },
confirmationDialog = { confirmationDialog = {
ConfirmationDialog( ConfirmationDialog(
title = stringResource(R.string.screen_room_change_role_unsaved_changes_title), title = stringResource(R.string.screen_room_change_role_unsaved_changes_title),
@ -119,6 +110,7 @@ fun ChangeRoomPermissionsView(
onErrorDismiss = {}, onErrorDismiss = {},
) )
} }
@Composable @Composable
private fun titleForSection(section: RoomPermissionsSection): String = when (section) { private fun titleForSection(section: RoomPermissionsSection): String = when (section) {
RoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details) RoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details)
@ -144,7 +136,7 @@ internal fun ChangeRoomPermissionsViewPreview(@PreviewParameter(ChangeRoomPermis
ElementPreview { ElementPreview {
ChangeRoomPermissionsView( ChangeRoomPermissionsView(
state = state, state = state,
onBackClick = {}, onComplete = {},
) )
} }
} }

View file

@ -21,6 +21,7 @@ import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRoles
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.appyx.launchMolecule import io.element.android.libraries.architecture.appyx.launchMolecule
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@ -40,17 +41,17 @@ class ChangeRolesNode(
private val presenter = presenterFactory.create(inputs.listType.toRoomMemberRole()) private val presenter = presenterFactory.create(inputs.listType.toRoomMemberRole())
private val stateFlow = launchMolecule { presenter.present() } private val stateFlow = launchMolecule { presenter.present() }
suspend fun waitForRoleChanged() { suspend fun waitForCompletion(): Boolean {
stateFlow.first { it.savingState.isSuccess() } val successState = stateFlow.first { it.savingState.isSuccess() }
return successState.savingState.dataOrNull().orFalse()
} }
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state by stateFlow.collectAsState() val state by stateFlow.collectAsState()
ChangeRolesView( ChangeRolesView(
modifier = modifier,
state = state, state = state,
navigateUp = this::navigateUp, modifier = modifier,
) )
} }
} }

View file

@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
@ -41,10 +42,12 @@ 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.NonCancellable
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
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@AssistedInject @AssistedInject
class ChangeRolesPresenter( class ChangeRolesPresenter(
@ -214,9 +217,13 @@ class ChangeRolesPresenter(
saveState.value = AsyncAction.Failure(it) saveState.value = AsyncAction.Failure(it)
} }
.onSuccess { .onSuccess {
saveState.value = AsyncAction.Success(true)
// Asynchronously reload the room members // Asynchronously reload the room members
launch { room.updateMembers() } launch {
withContext(NonCancellable) {
room.updateMembers()
}
}
saveState.value = AsyncAction.Success(true)
} }
} }
} }

View file

@ -29,8 +29,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.getValue
import androidx.compose.runtime.rememberUpdatedState
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
@ -42,7 +40,6 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.features.rolesandpermissions.impl.R import io.element.android.features.rolesandpermissions.impl.R
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
@ -77,10 +74,8 @@ import kotlinx.collections.immutable.ImmutableList
@Composable @Composable
fun ChangeRolesView( fun ChangeRolesView(
state: ChangeRolesState, state: ChangeRolesState,
navigateUp: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val latestNavigateUp by rememberUpdatedState(newValue = navigateUp)
BackHandler(enabled = !state.isSearchActive) { BackHandler(enabled = !state.isSearchActive) {
state.eventSink(ChangeRolesEvent.Exit) state.eventSink(ChangeRolesEvent.Exit)
} }
@ -168,18 +163,9 @@ fun ChangeRolesView(
val asyncIndicatorState = rememberAsyncIndicatorState() val asyncIndicatorState = rememberAsyncIndicatorState()
AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState) AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState)
AsyncActionView( AsyncActionView(
async = state.savingState, async = state.savingState,
onSuccess = { changeSaved -> onSuccess = {},
if (changeSaved) {
asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) {
AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes))
}
} else {
latestNavigateUp()
}
},
confirmationDialog = { confirming -> confirmationDialog = { confirming ->
when (confirming) { when (confirming) {
is AsyncAction.ConfirmingCancellation -> { is AsyncAction.ConfirmingCancellation -> {
@ -416,8 +402,7 @@ private fun MemberRow(
internal fun ChangeRolesViewPreview(@PreviewParameter(ChangeRolesStateProvider::class) state: ChangeRolesState) { internal fun ChangeRolesViewPreview(@PreviewParameter(ChangeRolesStateProvider::class) state: ChangeRolesState) {
ElementPreview { ElementPreview {
ChangeRolesView( ChangeRolesView(
state = state, state = state
navigateUp = {},
) )
} }
} }

View file

@ -21,7 +21,6 @@ import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode import io.element.android.annotations.ContributesNode
import io.element.android.appnav.di.RoomGraphFactory import io.element.android.appnav.di.RoomGraphFactory
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
@ -71,7 +70,7 @@ class ChangeRoomMemberRolesRootNode(
override val roomId: RoomId = inputs.joinedRoom.roomId override val roomId: RoomId = inputs.joinedRoom.roomId
override suspend fun waitForRoleChanged() { override suspend fun waitForCompletion(): Boolean {
waitForChildAttached<ChangeRolesNode>().waitForRoleChanged() return waitForChildAttached<ChangeRolesNode>().waitForCompletion()
} }
} }

View file

@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.room.toMatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.clickOn
@ -306,12 +305,10 @@ class ChangeRolesViewTest {
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRolesContent( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChangeRolesContent(
state: ChangeRolesState, state: ChangeRolesState,
onBackClick: () -> Unit = EnsureNeverCalled(),
) { ) {
setContent { setContent {
ChangeRolesView( ChangeRolesView(
state = state, state = state,
navigateUp = onBackClick,
) )
} }
} }

View file

@ -5,16 +5,16 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.features.rolesandpermissions.test package io.element.android.features.changeroommemberroles.test
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.Node
import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
class FakeChangeRoomMemberRolesEntryPoint : RolesAndPermissionsEntryPoint { class FakeChangeRoomMemberRolesEntryPoint : ChangeRoomMemberRolesEntryPoint {
override fun createNode( override fun createNode(
parentNode: Node, parentNode: Node,
buildContext: BuildContext, buildContext: BuildContext,

View file

@ -157,10 +157,12 @@ class RoomDetailsFlowNode(
changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy, changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy,
-> ->
commonLifecycle.coroutineScope.launch { commonLifecycle.coroutineScope.launch {
changeRoomMemberRolesNode.waitForRoleChanged() val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion()
withContext(NonCancellable) { withContext(NonCancellable) {
backstack.pop() backstack.pop()
roomDetailsNode.onNewOwnersSelected() if (isNewOwnerSelected) {
roomDetailsNode.onNewOwnersSelected()
}
} }
} }
} }

View file

@ -12,7 +12,7 @@ import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.call.test.FakeElementCallEntryPoint import io.element.android.features.call.test.FakeElementCallEntryPoint
import io.element.android.features.rolesandpermissions.test.FakeChangeRoomMemberRolesEntryPoint import io.element.android.features.changeroommemberroles.test.FakeChangeRoomMemberRolesEntryPoint
import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint
import io.element.android.features.messages.test.FakeMessagesEntryPoint import io.element.android.features.messages.test.FakeMessagesEntryPoint
import io.element.android.features.poll.test.history.FakePollHistoryEntryPoint import io.element.android.features.poll.test.history.FakePollHistoryEntryPoint