From c4b8227c58ea1ff1269eaefb9560f7007c33c2e3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Oct 2025 22:15:08 +0100 Subject: [PATCH 01/11] feature(space) : starts space settings screen --- .../impl/settings/SpaceSettingsEvents.kt | 10 + .../space/impl/settings/SpaceSettingsNode.kt | 58 +++++ .../impl/settings/SpaceSettingsPresenter.kt | 38 ++++ .../space/impl/settings/SpaceSettingsState.kt | 23 ++ .../settings/SpaceSettingsStateProvider.kt | 44 ++++ .../space/impl/settings/SpaceSettingsView.kt | 215 ++++++++++++++++++ 6 files changed, 388 insertions(+) create mode 100644 features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt create mode 100644 features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt create mode 100644 features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt create mode 100644 features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt create mode 100644 features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt create mode 100644 features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt new file mode 100644 index 0000000000..a6fe90ade6 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +sealed interface SpaceSettingsEvents diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt new file mode 100644 index 0000000000..77ae924f94 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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 com.bumble.appyx.core.plugin.plugins +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.space.impl.di.SpaceFlowScope +import io.element.android.libraries.architecture.appyx.launchMolecule + +@ContributesNode(SpaceFlowScope::class) +@AssistedInject +class SpaceSettingsNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: SpaceSettingsPresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onBackClick() + + fun onSpaceInfoClick() + fun onMembersClick() + fun onRolesAndPermissionsClick() + fun onSecurityAndPrivacyClick() + fun onLeaveSpaceClick() + } + + private val callback = plugins().single() + private val stateFlow = launchMolecule { presenter.present() } + + @Composable + override fun View(modifier: Modifier) { + val state by stateFlow.collectAsState() + SpaceSettingsView( + state = state, + modifier = modifier, + onSpaceInfoClick = callback::onSpaceInfoClick, + onBackClick = callback::onBackClick, + onMembersClick = callback::onMembersClick, + onRolesAndPermissionsClick = callback::onRolesAndPermissionsClick, + onSecurityAndPrivacyClick = callback::onSecurityAndPrivacyClick, + onLeaveSpaceClick = callback::onLeaveSpaceClick, + ) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt new file mode 100644 index 0000000000..48cb9f0b3b --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import dev.zacsweers.metro.Inject +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin + +@Inject +class SpaceSettingsPresenter( + private val room: JoinedRoom, +) : Presenter { + @Composable + override fun present(): SpaceSettingsState { + val roomInfo by room.roomInfoFlow.collectAsState() + val isUserAdmin = room.isOwnUserAdmin() + return SpaceSettingsState( + roomId = room.roomId, + name = roomInfo.name.orEmpty(), + canonicalAlias = roomInfo.canonicalAlias, + avatarUrl = roomInfo.avatarUrl, + memberCount = roomInfo.activeMembersCount, + showRolesAndPermissions = isUserAdmin, + showSecurityAndPrivacy = isUserAdmin, + isTombstoned = roomInfo.successorRoom != null, + eventSink = {}, + ) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt new file mode 100644 index 0000000000..b3d3353f06 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomId + +data class SpaceSettingsState( + val roomId: RoomId, + val name: String, + val canonicalAlias: RoomAlias?, + val avatarUrl: String?, + val isTombstoned: Boolean, + val memberCount: Long, + val showRolesAndPermissions: Boolean, + val showSecurityAndPrivacy: Boolean, + val eventSink: (SpaceSettingsEvents) -> Unit +) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt new file mode 100644 index 0000000000..e248c1798a --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomId + +open class SpaceSettingsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aSpaceSettingsState(), + aSpaceSettingsState(alias = null), + aSpaceSettingsState(showSecurityAndPrivacy = true), + aSpaceSettingsState(showRolesAndPermissions = true), + ) +} + +fun aSpaceSettingsState( + roomId: RoomId = RoomId("!aRoomId:element.io"), + name: String = "Space name", + alias: RoomAlias? = RoomAlias("#spacename:element.io"), + avatarUrl: String? = null, + memberCount: Long = 100, + isTombstoned: Boolean = false, + showRolesAndPermissions: Boolean = false, + showSecurityAndPrivacy: Boolean = false, + eventSink: (SpaceSettingsEvents) -> Unit = {}, +) = SpaceSettingsState( + roomId = roomId, + name = name, + canonicalAlias = alias, + avatarUrl = avatarUrl, + isTombstoned = isTombstoned, + memberCount = memberCount, + showRolesAndPermissions = showRolesAndPermissions, + showSecurityAndPrivacy = showSecurityAndPrivacy, + eventSink = eventSink, +) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt new file mode 100644 index 0000000000..eaefcf59d5 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt @@ -0,0 +1,215 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory +import io.element.android.libraries.designsystem.preview.ElementPreview +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.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun SpaceSettingsView( + state: SpaceSettingsState, + onBackClick: () -> Unit, + onSpaceInfoClick: ()->Unit, + onMembersClick: () -> Unit, + onRolesAndPermissionsClick: () -> Unit, + onSecurityAndPrivacyClick: () -> Unit, + onLeaveSpaceClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + topBar = { + SpaceSettingsTopBar(onBackClick = onBackClick) + }, + ) { padding -> + Column( + modifier = Modifier + .padding(padding) + .verticalScroll(rememberScrollState()) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onSpaceInfoClick) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Avatar( + avatarData = AvatarData(state.roomId.value, state.name, state.avatarUrl, AvatarSize.SpaceListItem), + avatarType = AvatarType.Space( + isTombstoned = state.isTombstoned, + ), + contentDescription = state.avatarUrl?.let { stringResource(CommonStrings.a11y_room_avatar) }, + ) + Spacer(Modifier.width(16.dp)) + Column { + Text( + text = state.name, + style = ElementTheme.typography.fontHeadingMdRegular, + color = ElementTheme.colors.textPrimary, + ) + if (state.canonicalAlias != null) { + Text( + text = state.canonicalAlias.value, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + } + Section(isVisible = state.showSecurityAndPrivacy) { + SecurityAndPrivacyItem( + onClick = onSecurityAndPrivacyClick + ) + } + Section { + MembersItem(state.memberCount, onClick = onMembersClick) + if (state.showRolesAndPermissions) { + RolesAndPermissionsItem(onClick = onRolesAndPermissionsClick) + } + } + Section { + LeaveSpaceItem( + onClick = onLeaveSpaceClick + ) + } + + } + } +} + +@Composable +private fun ColumnScope.Section( + modifier: Modifier = Modifier, + isVisible: Boolean = true, + content: @Composable ColumnScope.() -> Unit, +) { + if (isVisible) { + PreferenceCategory(content = content, modifier = modifier) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SpaceSettingsTopBar( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + TopAppBar( + titleStr = stringResource(CommonStrings.common_settings), + navigationIcon = { BackButton(onClick = onBackClick) }, + modifier = modifier, + ) +} + +@Composable +private fun SecurityAndPrivacyItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text("Security & privacy") }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun MembersItem( + memberCount: Long, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text(stringResource(CommonStrings.common_people)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.User())), + trailingContent = ListItemContent.Text(memberCount.toString()), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun RolesAndPermissionsItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text("Roles & permissions") }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun LeaveSpaceItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { + Text(stringResource(CommonStrings.action_leave_space)) + }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave())), + style = ListItemStyle.Destructive, + onClick = onClick, + modifier = modifier, + ) +} + +@PreviewsDayNight +@Composable +internal fun SpaceSettingsViewPreview( + @PreviewParameter(SpaceSettingsStateProvider::class) state: SpaceSettingsState +) = ElementPreview { + SpaceSettingsView( + state = state, + onBackClick = {}, + onSpaceInfoClick = {}, + onMembersClick = {}, + onRolesAndPermissionsClick = {}, + onSecurityAndPrivacyClick = {}, + onLeaveSpaceClick = {}, + modifier = Modifier, + ) +} From 9d3b7de0d8ff8b7a54a472a0c22d5cd45a2b65a2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Oct 2025 22:15:30 +0100 Subject: [PATCH 02/11] feature(space) : remove dead code # Conflicts: # appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt --- .../io/element/android/appnav/room/RoomFlowNode.kt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index a41eb8d777..3189384f53 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -30,7 +30,6 @@ import io.element.android.features.joinroom.api.JoinRoomEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params import io.element.android.features.roomdirectory.api.RoomDescription -import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs @@ -70,7 +69,6 @@ class RoomFlowNode( private val joinRoomEntryPoint: JoinRoomEntryPoint, private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint, private val membershipObserver: RoomMembershipObserver, - private val spaceEntryPoint: SpaceEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Loading, @@ -105,9 +103,6 @@ class RoomFlowNode( @Parcelize data class JoinedRoom(val roomId: RoomId) : NavTarget - - @Parcelize - data class JoinedSpace(val spaceId: RoomId) : NavTarget } override fun onBuilt() { @@ -209,15 +204,6 @@ class RoomFlowNode( ) createNode(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback) } - is NavTarget.JoinedSpace -> { - val spaceCallback = plugins().single() - spaceEntryPoint.createNode( - parentNode = this, - buildContext = buildContext, - inputs = SpaceEntryPoint.Inputs(roomId = navTarget.spaceId), - callback = spaceCallback, - ) - } } } From 5f7fda240279894de3a692778f576d0af223004f Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Oct 2025 22:16:20 +0100 Subject: [PATCH 03/11] feature(space) : plumb up space settings screen --- .../room/joined/JoinedRoomLoadedFlowNode.kt | 4 -- .../features/space/api/SpaceEntryPoint.kt | 1 - .../features/space/impl/SpaceFlowNode.kt | 47 ++++++++++++++++--- .../space/impl/leave/LeaveSpaceNode.kt | 7 ++- .../features/space/impl/root/SpaceNode.kt | 4 +- 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt index 16eaff89b1..d6e3e13e89 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt @@ -195,10 +195,6 @@ class JoinedRoomLoadedFlowNode( callback.navigateToRoom(roomId, viaParameters) } - override fun navigateToRoomDetails() { - backstack.push(NavTarget.RoomDetails) - } - override fun navigateToRoomMemberList() { backstack.push(NavTarget.RoomMemberList) } diff --git a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt index 6b5bd7f892..e05a7d1e8b 100644 --- a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt +++ b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt @@ -28,7 +28,6 @@ interface SpaceEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun navigateToRoom(roomId: RoomId, viaParameters: List) - fun navigateToRoomDetails() fun navigateToRoomMemberList() } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index 1ef496d319..6684d07952 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -18,6 +18,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject @@ -26,14 +27,15 @@ import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowGraph import io.element.android.features.space.impl.leave.LeaveSpaceNode import io.element.android.features.space.impl.root.SpaceNode +import io.element.android.features.space.impl.settings.SpaceSettingsNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.DependencyInjectionGraphOwner import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.spaces.SpaceService import kotlinx.parcelize.Parcelize @@ -42,6 +44,7 @@ import kotlinx.parcelize.Parcelize class SpaceFlowNode( @Assisted val buildContext: BuildContext, @Assisted plugins: List, + room: BaseRoom, spaceService: SpaceService, graphFactory: SpaceFlowGraph.Factory, ) : BaseFlowNode( @@ -52,15 +55,17 @@ class SpaceFlowNode( buildContext = buildContext, plugins = plugins, ), DependencyInjectionGraphOwner { - private val inputs: SpaceEntryPoint.Inputs = inputs() private val callback: SpaceEntryPoint.Callback = callback() - private val spaceRoomList = spaceService.spaceRoomList(inputs.roomId) + private val spaceRoomList = spaceService.spaceRoomList(room.roomId) override val graph = graphFactory.create(spaceRoomList) sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget + @Parcelize + data object Settings : NavTarget + @Parcelize data object Leave : NavTarget } @@ -77,7 +82,7 @@ class SpaceFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Leave -> { - createNode(buildContext, listOf(inputs)) + createNode(buildContext) } NavTarget.Root -> { val callback = object : SpaceNode.Callback { @@ -85,8 +90,8 @@ class SpaceFlowNode( callback.navigateToRoom(roomId, viaParameters) } - override fun navigateToRoomDetails() { - callback.navigateToRoomDetails() + override fun navigateToSpaceSettings() { + backstack.push(NavTarget.Settings) } override fun navigateToRoomMemberList() { @@ -97,7 +102,35 @@ class SpaceFlowNode( backstack.push(NavTarget.Leave) } } - createNode(buildContext, listOf(inputs, callback)) + createNode(buildContext, listOf(callback)) + } + NavTarget.Settings -> { + val callback = object : SpaceSettingsNode.Callback { + override fun onBackClick() { + backstack.pop() + } + + override fun onSpaceInfoClick() { + //TODO + } + + override fun onMembersClick() { + callback.navigateToRoomMemberList() + } + + override fun onRolesAndPermissionsClick() { + //TODO + } + + override fun onSecurityAndPrivacyClick() { + //TODO + } + + override fun onLeaveSpaceClick() { + backstack.push(NavTarget.Leave) + } + } + createNode(buildContext, listOf(callback)) } } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt index c60bddea1d..215f06bca5 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt @@ -16,10 +16,9 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowScope -import io.element.android.libraries.architecture.inputs import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.JoinedRoom @ContributesNode(SpaceFlowScope::class) @AssistedInject @@ -27,10 +26,10 @@ class LeaveSpaceNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, matrixClient: MatrixClient, + room: JoinedRoom, presenterFactory: LeaveSpacePresenter.Factory, ) : Node(buildContext, plugins = plugins) { - private val inputs: SpaceEntryPoint.Inputs = inputs() - private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(inputs.roomId) + private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(room.roomId) private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle) override fun onBuilt() { diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt index 174fa71ee8..c0271782b4 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt @@ -42,7 +42,7 @@ class SpaceNode( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun navigateToRoom(roomId: RoomId, viaParameters: List) - fun navigateToRoomDetails() + fun navigateToSpaceSettings() fun navigateToRoomMemberList() fun startLeaveSpaceFlow() } @@ -80,7 +80,7 @@ class SpaceNode( callback.navigateToRoom(spaceRoom.roomId, spaceRoom.via) }, onDetailsClick = { - callback.navigateToRoomDetails() + callback.navigateToSpaceSettings() }, onShareSpace = { onShareRoom(context) From 4c9b6100a76f723f743114d34f76d5a2ffb55c20 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 31 Oct 2025 14:33:11 +0100 Subject: [PATCH 04/11] feature(space) : iterate on SpaceSettings --- .../impl/settings/SpaceSettingsPresenter.kt | 1 - .../space/impl/settings/SpaceSettingsState.kt | 1 - .../settings/SpaceSettingsStateProvider.kt | 2 - .../space/impl/settings/SpaceSettingsView.kt | 92 +++++++++++-------- 4 files changed, 54 insertions(+), 42 deletions(-) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt index 48cb9f0b3b..6e89a0ba8d 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt @@ -31,7 +31,6 @@ class SpaceSettingsPresenter( memberCount = roomInfo.activeMembersCount, showRolesAndPermissions = isUserAdmin, showSecurityAndPrivacy = isUserAdmin, - isTombstoned = roomInfo.successorRoom != null, eventSink = {}, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt index b3d3353f06..95b3615f63 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt @@ -15,7 +15,6 @@ data class SpaceSettingsState( val name: String, val canonicalAlias: RoomAlias?, val avatarUrl: String?, - val isTombstoned: Boolean, val memberCount: Long, val showRolesAndPermissions: Boolean, val showSecurityAndPrivacy: Boolean, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt index e248c1798a..db1b336653 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt @@ -27,7 +27,6 @@ fun aSpaceSettingsState( alias: RoomAlias? = RoomAlias("#spacename:element.io"), avatarUrl: String? = null, memberCount: Long = 100, - isTombstoned: Boolean = false, showRolesAndPermissions: Boolean = false, showSecurityAndPrivacy: Boolean = false, eventSink: (SpaceSettingsEvents) -> Unit = {}, @@ -36,7 +35,6 @@ fun aSpaceSettingsState( name = name, canonicalAlias = alias, avatarUrl = avatarUrl, - isTombstoned = isTombstoned, memberCount = memberCount, showRolesAndPermissions = showRolesAndPermissions, showSecurityAndPrivacy = showSecurityAndPrivacy, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt index eaefcf59d5..1775b1e5b1 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt @@ -41,13 +41,14 @@ import io.element.android.libraries.designsystem.theme.components.ListItemStyle 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.TopAppBar +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.ui.strings.CommonStrings @Composable fun SpaceSettingsView( state: SpaceSettingsState, onBackClick: () -> Unit, - onSpaceInfoClick: ()->Unit, + onSpaceInfoClick: () -> Unit, onMembersClick: () -> Unit, onRolesAndPermissionsClick: () -> Unit, onSecurityAndPrivacyClick: () -> Unit, @@ -65,59 +66,74 @@ fun SpaceSettingsView( .padding(padding) .verticalScroll(rememberScrollState()) ) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable(onClick = onSpaceInfoClick) - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Avatar( - avatarData = AvatarData(state.roomId.value, state.name, state.avatarUrl, AvatarSize.SpaceListItem), - avatarType = AvatarType.Space( - isTombstoned = state.isTombstoned, - ), - contentDescription = state.avatarUrl?.let { stringResource(CommonStrings.a11y_room_avatar) }, - ) - Spacer(Modifier.width(16.dp)) - Column { - Text( - text = state.name, - style = ElementTheme.typography.fontHeadingMdRegular, - color = ElementTheme.colors.textPrimary, - ) - if (state.canonicalAlias != null) { - Text( - text = state.canonicalAlias.value, - style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textSecondary, - ) - } - } - } - Section(isVisible = state.showSecurityAndPrivacy) { + SpaceInfoSection( + roomId = state.roomId, + name = state.name, + avatarUrl = state.avatarUrl, + canonicalAlias = state.canonicalAlias?.value, + onSpaceInfoClick = onSpaceInfoClick, + ) + Section(isVisible = state.showSecurityAndPrivacy, content = { SecurityAndPrivacyItem( onClick = onSecurityAndPrivacyClick ) - } - Section { + }) + Section(content = { MembersItem(state.memberCount, onClick = onMembersClick) if (state.showRolesAndPermissions) { RolesAndPermissionsItem(onClick = onRolesAndPermissionsClick) } - } - Section { + }) + Section(content = { LeaveSpaceItem( onClick = onLeaveSpaceClick ) - } + }) } } } @Composable -private fun ColumnScope.Section( +private fun SpaceInfoSection( + roomId: RoomId, + name: String, + avatarUrl: String?, + canonicalAlias: String?, + onSpaceInfoClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onSpaceInfoClick) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Avatar( + avatarData = AvatarData(roomId.value, name, avatarUrl, AvatarSize.SpaceListItem), + avatarType = AvatarType.Space(), + contentDescription = avatarUrl?.let { stringResource(CommonStrings.a11y_avatar) }, + ) + Spacer(Modifier.width(16.dp)) + Column { + Text( + text = name, + style = ElementTheme.typography.fontHeadingMdRegular, + color = ElementTheme.colors.textPrimary, + ) + if (canonicalAlias != null) { + Text( + text = canonicalAlias, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + } +} + +@Composable +private fun Section( modifier: Modifier = Modifier, isVisible: Boolean = true, content: @Composable ColumnScope.() -> Unit, From 2d8f491cfddcd22e4ee574dea369b9f7381d4e55 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 31 Oct 2025 14:55:07 +0100 Subject: [PATCH 05/11] feature(space) : update some strings --- .../impl/src/main/res/values/localazy.xml | 2 +- features/roomdetails/impl/src/main/res/values/localazy.xml | 2 +- .../impl/src/main/res/values/localazy.xml | 2 ++ .../io/element/android/features/space/impl/root/SpaceView.kt | 2 +- .../features/space/impl/settings/SpaceSettingsView.kt | 5 +++-- features/space/impl/src/main/res/values/localazy.xml | 3 +++ libraries/ui-strings/src/main/res/values/localazy.xml | 2 +- tools/localazy/config.json | 3 ++- 8 files changed, 14 insertions(+), 7 deletions(-) diff --git a/features/changeroommemberroles/impl/src/main/res/values/localazy.xml b/features/changeroommemberroles/impl/src/main/res/values/localazy.xml index 456426726a..43f6dc10f8 100644 --- a/features/changeroommemberroles/impl/src/main/res/values/localazy.xml +++ b/features/changeroommemberroles/impl/src/main/res/values/localazy.xml @@ -33,7 +33,7 @@ "Members" "You have unsaved changes." "Save changes?" - "There are no banned users in this room." + "There are no banned users." "%1$d person" "%1$d people" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index ce8eb3a7b7..814f352abd 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -70,7 +70,7 @@ "Room info" "Topic" "Updating room…" - "There are no banned users in this room." + "There are no banned users." "%1$d person" "%1$d people" diff --git a/features/roommembermoderation/impl/src/main/res/values/localazy.xml b/features/roommembermoderation/impl/src/main/res/values/localazy.xml index e3f6071898..3d23c8763a 100644 --- a/features/roommembermoderation/impl/src/main/res/values/localazy.xml +++ b/features/roommembermoderation/impl/src/main/res/values/localazy.xml @@ -4,10 +4,12 @@ "Ban" "They won’t be able to join again if invited." "Are you sure you want to ban this member?" + "They won’t be able to join this space again if invited, but they’ll still keep their memberships of any rooms or subspaces." "Banning %1$s" "Remove" "They will be able to join this room again if invited." "Are you sure you want to remove this member?" + "They will be able to join this space again if invited, and they’ll still keep their memberships of any rooms or subspaces." "View profile" "Remove user" "Remove member and ban from joining in the future?" diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt index f4c415b718..2779ab2687 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt @@ -328,7 +328,7 @@ private fun SpaceViewTopBar( }, text = { Text( - text = stringResource(id = CommonStrings.action_leave), + text = stringResource(id = CommonStrings.action_leave_space), color = ElementTheme.colors.textCriticalPrimary, ) }, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt index 1775b1e5b1..9689b9e4f1 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.space.impl.R import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -162,7 +163,7 @@ private fun SecurityAndPrivacyItem( modifier: Modifier = Modifier, ) { ListItem( - headlineContent = { Text("Security & privacy") }, + headlineContent = { Text(stringResource(R.string.screen_space_settings_security_and_privacy)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())), onClick = onClick, modifier = modifier, @@ -190,7 +191,7 @@ private fun RolesAndPermissionsItem( modifier: Modifier = Modifier, ) { ListItem( - headlineContent = { Text("Roles & permissions") }, + headlineContent = { Text(stringResource(R.string.screen_space_settings_roles_and_permissions)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())), onClick = onClick, modifier = modifier, diff --git a/features/space/impl/src/main/res/values/localazy.xml b/features/space/impl/src/main/res/values/localazy.xml index c6ced29d41..a4df5e767d 100644 --- a/features/space/impl/src/main/res/values/localazy.xml +++ b/features/space/impl/src/main/res/values/localazy.xml @@ -10,4 +10,7 @@ "You will not be removed from the following room(s) because you\'re the only administrator:" "Leave %1$s?" "You are the only admin for %1$s" + "Leave space" + "Roles & permissions" + "Security & privacy" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 8ca9198cfe..7bf9e8192c 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -95,6 +95,7 @@ "Forgot password?" "Forward" "Go back" + "Go to roles & permissions" "Go to settings" "Ignore" "Invite" @@ -176,7 +177,6 @@ "Advanced settings" "an image" "Analytics" - "Fetching notifications…" "You left the room" "You were logged out of the session" "Appearance" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 82c273db2e..3039631253 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -210,7 +210,8 @@ { "name" : ":features:space:impl", "includeRegex" : [ - "screen\\.leave_space\\..*" + "screen\\.leave_space\\..*", + "screen\\.space_settings\\..*" ] }, { From f844007812bb754df4276fc56b47fe28ac6c9e23 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 31 Oct 2025 15:10:55 +0100 Subject: [PATCH 06/11] feature(space) : some renaming on Space nodes --- .../features/space/impl/SpaceFlowNode.kt | 12 +++++----- .../space/impl/settings/SpaceSettingsNode.kt | 24 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index 6684d07952..ba55203be6 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -106,27 +106,27 @@ class SpaceFlowNode( } NavTarget.Settings -> { val callback = object : SpaceSettingsNode.Callback { - override fun onBackClick() { + override fun closeSettings() { backstack.pop() } - override fun onSpaceInfoClick() { + override fun navigateToSpaceInfo() { //TODO } - override fun onMembersClick() { + override fun navigateToSpaceMembers() { callback.navigateToRoomMemberList() } - override fun onRolesAndPermissionsClick() { + override fun navigateToRolesAndPermissions() { //TODO } - override fun onSecurityAndPrivacyClick() { + override fun navigateToSecurityAndPrivacy() { //TODO } - override fun onLeaveSpaceClick() { + override fun startLeaveSpaceFlow() { backstack.push(NavTarget.Leave) } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt index 77ae924f94..9b81272153 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt @@ -29,13 +29,13 @@ class SpaceSettingsNode( private val presenter: SpaceSettingsPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onBackClick() + fun closeSettings() - fun onSpaceInfoClick() - fun onMembersClick() - fun onRolesAndPermissionsClick() - fun onSecurityAndPrivacyClick() - fun onLeaveSpaceClick() + fun navigateToSpaceInfo() + fun navigateToSpaceMembers() + fun navigateToRolesAndPermissions() + fun navigateToSecurityAndPrivacy() + fun startLeaveSpaceFlow() } private val callback = plugins().single() @@ -47,12 +47,12 @@ class SpaceSettingsNode( SpaceSettingsView( state = state, modifier = modifier, - onSpaceInfoClick = callback::onSpaceInfoClick, - onBackClick = callback::onBackClick, - onMembersClick = callback::onMembersClick, - onRolesAndPermissionsClick = callback::onRolesAndPermissionsClick, - onSecurityAndPrivacyClick = callback::onSecurityAndPrivacyClick, - onLeaveSpaceClick = callback::onLeaveSpaceClick, + onSpaceInfoClick = callback::navigateToSpaceInfo, + onBackClick = callback::closeSettings, + onMembersClick = callback::navigateToSpaceMembers, + onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions, + onSecurityAndPrivacyClick = callback::navigateToSecurityAndPrivacy, + onLeaveSpaceClick = callback::startLeaveSpaceFlow, ) } } From 48d2696ba87e41b13ac11b63bc41289c1834dbc1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 31 Oct 2025 15:32:30 +0100 Subject: [PATCH 07/11] feature(space) : prepare LeaveSpace for navigation to Roles&Permissions --- .../features/space/impl/SpaceFlowNode.kt | 17 +++++++++++++---- .../features/space/impl/leave/LeaveSpaceNode.kt | 11 ++++++++++- .../features/space/impl/leave/LeaveSpaceView.kt | 17 +++++++++++++++-- .../space/impl/settings/SpaceSettingsView.kt | 1 - 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index ba55203be6..2df48350c6 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -82,7 +82,16 @@ class SpaceFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Leave -> { - createNode(buildContext) + val callback = object : LeaveSpaceNode.Callback { + override fun closeLeaveSpaceFlow() { + backstack.pop() + } + + override fun navigateToRolesAndPermissions() { + // TODO + } + } + createNode(buildContext, listOf(callback)) } NavTarget.Root -> { val callback = object : SpaceNode.Callback { @@ -111,7 +120,7 @@ class SpaceFlowNode( } override fun navigateToSpaceInfo() { - //TODO + // TODO } override fun navigateToSpaceMembers() { @@ -119,11 +128,11 @@ class SpaceFlowNode( } override fun navigateToRolesAndPermissions() { - //TODO + // TODO } override fun navigateToSecurityAndPrivacy() { - //TODO + // TODO } override fun startLeaveSpaceFlow() { diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt index 215f06bca5..6ba86481fe 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt @@ -13,6 +13,7 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode @@ -29,9 +30,16 @@ class LeaveSpaceNode( room: JoinedRoom, presenterFactory: LeaveSpacePresenter.Factory, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun closeLeaveSpaceFlow() + fun navigateToRolesAndPermissions() + } + private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(room.roomId) private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle) + private val callback = plugins().single() + override fun onBuilt() { super.onBuilt() lifecycle.subscribe( @@ -46,7 +54,8 @@ class LeaveSpaceNode( val state = presenter.present() LeaveSpaceView( state = state, - onCancel = ::navigateUp, + onCancel = callback::closeLeaveSpaceFlow, + onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions, modifier = modifier ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt index 7432301f91..6f7a9903df 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt @@ -69,6 +69,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun LeaveSpaceView( state: LeaveSpaceState, onCancel: () -> Unit, + onRolesAndPermissionsClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -130,6 +131,9 @@ fun LeaveSpaceView( state.eventSink(LeaveSpaceEvents.LeaveSpace) }, onCancel = onCancel, + // TODO enable when navigation is ready + showRolesAndPermissionsButton = false, // state.isLastAdmin, + onRolesAndPermissionsClick = onRolesAndPermissionsClick, ) } } @@ -210,6 +214,8 @@ private fun LeaveSpaceButtons( showLeaveButton: Boolean, selectedRoomsCount: Int, onLeaveSpace: () -> Unit, + showRolesAndPermissionsButton: Boolean, + onRolesAndPermissionsClick: () -> Unit, onCancel: () -> Unit, ) { ButtonColumnMolecule( @@ -229,8 +235,14 @@ private fun LeaveSpaceButtons( destructive = true, ) } - // TODO For least admin space, add a button to open the settings. - // See https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4622-59600 + if (showRolesAndPermissionsButton) { + Button( + text = stringResource(CommonStrings.action_go_to_roles_and_permissions), + onClick = onRolesAndPermissionsClick, + modifier = Modifier.fillMaxWidth(), + leadingIcon = IconSource.Vector(CompoundIcons.Settings()), + ) + } TextButton( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_cancel), @@ -345,5 +357,6 @@ internal fun LeaveSpaceViewPreview( LeaveSpaceView( state = state, onCancel = {}, + onRolesAndPermissionsClick = {}, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt index 9689b9e4f1..fae5bf2f03 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt @@ -90,7 +90,6 @@ fun SpaceSettingsView( onClick = onLeaveSpaceClick ) }) - } } } From 4097ff26268c35dfd87b69c3d434f2c8b81486cf Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 31 Oct 2025 15:34:55 +0100 Subject: [PATCH 08/11] feature(space) : some code clean up --- .../io/element/android/features/space/impl/SpaceFlowNode.kt | 4 ++-- .../android/features/space/impl/DefaultSpaceEntryPointTest.kt | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index 2df48350c6..686729bae5 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -35,7 +35,7 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.DependencyInjectionGraphOwner import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.BaseRoom +import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.spaces.SpaceService import kotlinx.parcelize.Parcelize @@ -44,7 +44,7 @@ import kotlinx.parcelize.Parcelize class SpaceFlowNode( @Assisted val buildContext: BuildContext, @Assisted plugins: List, - room: BaseRoom, + room: JoinedRoom, spaceService: SpaceService, graphFactory: SpaceFlowGraph.Factory, ) : BaseFlowNode( diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt index 3fd260dd4f..696e80eeea 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt @@ -15,6 +15,8 @@ import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.FakeSpaceFlowGraph import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.tests.testutils.lambda.lambdaError @@ -40,12 +42,12 @@ class DefaultSpaceEntryPointTest { spaceService = FakeSpaceService( spaceRoomListResult = { _: RoomId -> FakeSpaceRoomList(A_ROOM_ID) } ), + room = FakeJoinedRoom(), graphFactory = FakeSpaceFlowGraph.Factory ) } val callback = object : SpaceEntryPoint.Callback { override fun navigateToRoom(roomId: RoomId, viaParameters: List) = lambdaError() - override fun navigateToRoomDetails() = lambdaError() override fun navigateToRoomMemberList() = lambdaError() } val result = entryPoint.createNode( From ca9576238d20ace652662c909473c3e33ec29d77 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 3 Nov 2025 14:16:13 +0000 Subject: [PATCH 09/11] Update screenshots --- ...features.space.impl.settings_SpaceSettingsView_Day_0_en.png | 3 +++ ...features.space.impl.settings_SpaceSettingsView_Day_1_en.png | 3 +++ ...features.space.impl.settings_SpaceSettingsView_Day_2_en.png | 3 +++ ...features.space.impl.settings_SpaceSettingsView_Day_3_en.png | 3 +++ ...atures.space.impl.settings_SpaceSettingsView_Night_0_en.png | 3 +++ ...atures.space.impl.settings_SpaceSettingsView_Night_1_en.png | 3 +++ ...atures.space.impl.settings_SpaceSettingsView_Night_2_en.png | 3 +++ ...atures.space.impl.settings_SpaceSettingsView_Night_3_en.png | 3 +++ 8 files changed, 24 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_3_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_0_en.png new file mode 100644 index 0000000000..24c00cb1a6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a573dcac3bab78db152da81c0a1b7803fcf70c2392cc05a20ae533edfde76730 +size 21620 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_1_en.png new file mode 100644 index 0000000000..13d9ea54d2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a17f1005bf296f04a2d631c6d19313bc864ebcd6832a788f8fa6909b7562dcb5 +size 17874 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_2_en.png new file mode 100644 index 0000000000..f408da9f38 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc34deb76678be310df0beb512d91fa3b6edf2d2c59a7daf46267f1c8012e12c +size 25422 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_3_en.png new file mode 100644 index 0000000000..189eb42ad0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14d4cabaf98490e35bb75a12c3d37e3c499158b6d6b639084059ee1bc999e831 +size 26018 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_0_en.png new file mode 100644 index 0000000000..d163b60818 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b13402b213b595b8a0a632eeb96263db7e8f7cbd3f56d8d235c38abf6df66128 +size 21235 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_1_en.png new file mode 100644 index 0000000000..77db74b048 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7031872033a6c12acd7328a5ae0820ac2dfa00340f8a8b6cac746c98004b285c +size 17444 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_2_en.png new file mode 100644 index 0000000000..9d45abb1c6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:053e0d74fb1c01e5e434aa48606b5f0d24fb9a950b56555783b618679c4e7ad5 +size 24925 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_3_en.png new file mode 100644 index 0000000000..9c36d80cef --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.settings_SpaceSettingsView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d201e92db03d2d0d1b7d786d472d5d0f3e946ba7b610b9769ceb597b4d827eb9 +size 25467 From 71ef154eea693297e51da4708c36f8c22bd54a67 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 3 Nov 2025 20:27:37 +0100 Subject: [PATCH 10/11] quality: fix import in test --- .../android/features/space/impl/DefaultSpaceEntryPointTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt index 696e80eeea..319d1eeeff 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt @@ -16,7 +16,6 @@ import io.element.android.features.space.impl.di.FakeSpaceFlowGraph import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.room.FakeJoinedRoom -import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.tests.testutils.lambda.lambdaError From f4ef9a0545b2f113e3b7f8d5dc277ae3ab084734 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 5 Nov 2025 15:42:59 +0100 Subject: [PATCH 11/11] quality: use callback() method in nodes --- .../android/features/space/impl/leave/LeaveSpaceNode.kt | 4 ++-- .../android/features/space/impl/settings/SpaceSettingsNode.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt index 6ba86481fe..6eaa5d4e4a 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt @@ -13,11 +13,11 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.space.impl.di.SpaceFlowScope +import io.element.android.libraries.architecture.callback import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -38,7 +38,7 @@ class LeaveSpaceNode( private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(room.roomId) private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle) - private val callback = plugins().single() + private val callback: Callback = callback() override fun onBuilt() { super.onBuilt() diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt index 9b81272153..ea2e07db77 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt @@ -14,12 +14,12 @@ 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 com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.space.impl.di.SpaceFlowScope import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.architecture.callback @ContributesNode(SpaceFlowScope::class) @AssistedInject @@ -38,7 +38,7 @@ class SpaceSettingsNode( fun startLeaveSpaceFlow() } - private val callback = plugins().single() + private val callback: Callback = callback() private val stateFlow = launchMolecule { presenter.present() } @Composable