Merge pull request #5668 from element-hq/feature/fga/space_settings
Space : prepare Space Settings screen
This commit is contained in:
@@ -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<RoomFlowNode.NavTarget>(
|
||||
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<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
|
||||
}
|
||||
is NavTarget.JoinedSpace -> {
|
||||
val spaceCallback = plugins<SpaceEntryPoint.Callback>().single()
|
||||
spaceEntryPoint.createNode(
|
||||
parentNode = this,
|
||||
buildContext = buildContext,
|
||||
inputs = SpaceEntryPoint.Inputs(roomId = navTarget.spaceId),
|
||||
callback = spaceCallback,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -197,10 +197,6 @@ class JoinedRoomLoadedFlowNode(
|
||||
callback.navigateToRoom(roomId, viaParameters)
|
||||
}
|
||||
|
||||
override fun navigateToRoomDetails() {
|
||||
backstack.push(NavTarget.RoomDetails)
|
||||
}
|
||||
|
||||
override fun navigateToRoomMemberList() {
|
||||
backstack.push(NavTarget.RoomMemberList)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ interface SpaceEntryPoint : FeatureEntryPoint {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun navigateToRoom(roomId: RoomId, viaParameters: List<String>)
|
||||
fun navigateToRoomDetails()
|
||||
fun navigateToRoomMemberList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.JoinedRoom
|
||||
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<Plugin>,
|
||||
room: JoinedRoom,
|
||||
spaceService: SpaceService,
|
||||
graphFactory: SpaceFlowGraph.Factory,
|
||||
) : BaseFlowNode<SpaceFlowNode.NavTarget>(
|
||||
@@ -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,16 @@ class SpaceFlowNode(
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Leave -> {
|
||||
createNode<LeaveSpaceNode>(buildContext, listOf(inputs))
|
||||
val callback = object : LeaveSpaceNode.Callback {
|
||||
override fun closeLeaveSpaceFlow() {
|
||||
backstack.pop()
|
||||
}
|
||||
|
||||
override fun navigateToRolesAndPermissions() {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
createNode<LeaveSpaceNode>(buildContext, listOf(callback))
|
||||
}
|
||||
NavTarget.Root -> {
|
||||
val callback = object : SpaceNode.Callback {
|
||||
@@ -85,8 +99,8 @@ class SpaceFlowNode(
|
||||
callback.navigateToRoom(roomId, viaParameters)
|
||||
}
|
||||
|
||||
override fun navigateToRoomDetails() {
|
||||
callback.navigateToRoomDetails()
|
||||
override fun navigateToSpaceSettings() {
|
||||
backstack.push(NavTarget.Settings)
|
||||
}
|
||||
|
||||
override fun navigateToRoomMemberList() {
|
||||
@@ -97,7 +111,35 @@ class SpaceFlowNode(
|
||||
backstack.push(NavTarget.Leave)
|
||||
}
|
||||
}
|
||||
createNode<SpaceNode>(buildContext, listOf(inputs, callback))
|
||||
createNode<SpaceNode>(buildContext, listOf(callback))
|
||||
}
|
||||
NavTarget.Settings -> {
|
||||
val callback = object : SpaceSettingsNode.Callback {
|
||||
override fun closeSettings() {
|
||||
backstack.pop()
|
||||
}
|
||||
|
||||
override fun navigateToSpaceInfo() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun navigateToSpaceMembers() {
|
||||
callback.navigateToRoomMemberList()
|
||||
}
|
||||
|
||||
override fun navigateToRolesAndPermissions() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun navigateToSecurityAndPrivacy() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun startLeaveSpaceFlow() {
|
||||
backstack.push(NavTarget.Leave)
|
||||
}
|
||||
}
|
||||
createNode<SpaceSettingsNode>(buildContext, listOf(callback))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ 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.architecture.callback
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
@ContributesNode(SpaceFlowScope::class)
|
||||
@AssistedInject
|
||||
@@ -27,12 +27,19 @@ class LeaveSpaceNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
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)
|
||||
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: Callback = callback()
|
||||
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
lifecycle.subscribe(
|
||||
@@ -47,7 +54,8 @@ class LeaveSpaceNode(
|
||||
val state = presenter.present()
|
||||
LeaveSpaceView(
|
||||
state = state,
|
||||
onCancel = ::navigateUp,
|
||||
onCancel = callback::closeLeaveSpaceFlow,
|
||||
onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class SpaceNode(
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun navigateToRoom(roomId: RoomId, viaParameters: List<String>)
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
},
|
||||
|
||||
@@ -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
|
||||
@@ -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 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
|
||||
class SpaceSettingsNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: SpaceSettingsPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : Plugin {
|
||||
fun closeSettings()
|
||||
|
||||
fun navigateToSpaceInfo()
|
||||
fun navigateToSpaceMembers()
|
||||
fun navigateToRolesAndPermissions()
|
||||
fun navigateToSecurityAndPrivacy()
|
||||
fun startLeaveSpaceFlow()
|
||||
}
|
||||
|
||||
private val callback: Callback = callback()
|
||||
private val stateFlow = launchMolecule { presenter.present() }
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state by stateFlow.collectAsState()
|
||||
SpaceSettingsView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onSpaceInfoClick = callback::navigateToSpaceInfo,
|
||||
onBackClick = callback::closeSettings,
|
||||
onMembersClick = callback::navigateToSpaceMembers,
|
||||
onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions,
|
||||
onSecurityAndPrivacyClick = callback::navigateToSecurityAndPrivacy,
|
||||
onLeaveSpaceClick = callback::startLeaveSpaceFlow,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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<SpaceSettingsState> {
|
||||
@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,
|
||||
eventSink = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 memberCount: Long,
|
||||
val showRolesAndPermissions: Boolean,
|
||||
val showSecurityAndPrivacy: Boolean,
|
||||
val eventSink: (SpaceSettingsEvents) -> Unit
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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<SpaceSettingsState> {
|
||||
override val values: Sequence<SpaceSettingsState>
|
||||
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,
|
||||
showRolesAndPermissions: Boolean = false,
|
||||
showSecurityAndPrivacy: Boolean = false,
|
||||
eventSink: (SpaceSettingsEvents) -> Unit = {},
|
||||
) = SpaceSettingsState(
|
||||
roomId = roomId,
|
||||
name = name,
|
||||
canonicalAlias = alias,
|
||||
avatarUrl = avatarUrl,
|
||||
memberCount = memberCount,
|
||||
showRolesAndPermissions = showRolesAndPermissions,
|
||||
showSecurityAndPrivacy = showSecurityAndPrivacy,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.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
|
||||
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.matrix.api.core.RoomId
|
||||
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())
|
||||
) {
|
||||
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(content = {
|
||||
MembersItem(state.memberCount, onClick = onMembersClick)
|
||||
if (state.showRolesAndPermissions) {
|
||||
RolesAndPermissionsItem(onClick = onRolesAndPermissionsClick)
|
||||
}
|
||||
})
|
||||
Section(content = {
|
||||
LeaveSpaceItem(
|
||||
onClick = onLeaveSpaceClick
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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,
|
||||
) {
|
||||
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(stringResource(R.string.screen_space_settings_security_and_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(stringResource(R.string.screen_space_settings_roles_and_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,
|
||||
)
|
||||
}
|
||||
@@ -10,4 +10,7 @@
|
||||
<string name="screen_leave_space_subtitle_only_last_admin">"You will not be removed from the following room(s) because you\'re the only administrator:"</string>
|
||||
<string name="screen_leave_space_title">"Leave %1$s?"</string>
|
||||
<string name="screen_leave_space_title_last_admin">"You are the only admin for %1$s"</string>
|
||||
<string name="screen_space_settings_leave_space">"Leave space"</string>
|
||||
<string name="screen_space_settings_roles_and_permissions">"Roles & permissions"</string>
|
||||
<string name="screen_space_settings_security_and_privacy">"Security & privacy"</string>
|
||||
</resources>
|
||||
|
||||
@@ -15,6 +15,7 @@ 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.spaces.FakeSpaceRoomList
|
||||
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
@@ -40,12 +41,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<String>) = lambdaError()
|
||||
override fun navigateToRoomDetails() = lambdaError()
|
||||
override fun navigateToRoomMemberList() = lambdaError()
|
||||
}
|
||||
val result = entryPoint.createNode(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -210,7 +210,8 @@
|
||||
{
|
||||
"name" : ":features:space:impl",
|
||||
"includeRegex" : [
|
||||
"screen\\.leave_space\\..*"
|
||||
"screen\\.leave_space\\..*",
|
||||
"screen\\.space_settings\\..*"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user