feature(security&privacy): start ManageAuthorizedSpacesView

This commit is contained in:
ganfra
2025-12-18 10:07:57 +01:00
parent d868bf64bf
commit 7b8950a51b
4 changed files with 171 additions and 6 deletions

View File

@@ -18,6 +18,7 @@ import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNav
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.room.JoinedRoom
import kotlinx.collections.immutable.persistentListOf
@AssistedInject
class EditRoomAddressPresenter(
@@ -33,7 +34,6 @@ class EditRoomAddressPresenter(
@Composable
override fun present(): ManageAuthorizedSpacesState {
val roomInfo by room.roomInfoFlow.collectAsState()
fun handleEvent(event: ManageAuthorizedSpacesEvent) {
when (event) {
ManageAuthorizedSpacesEvent.Done -> TODO()
@@ -42,6 +42,10 @@ class EditRoomAddressPresenter(
}
return ManageAuthorizedSpacesState(
joinedSpaces = persistentListOf(),
unknownSpaceIds = persistentListOf(),
currentSelection = persistentListOf(),
initialSelection = persistentListOf(),
eventSink = ::handleEvent,
)
}

View File

@@ -8,6 +8,14 @@
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import kotlinx.collections.immutable.ImmutableList
data class ManageAuthorizedSpacesState(
val joinedSpaces: ImmutableList<SpaceRoom>,
val unknownSpaceIds: ImmutableList<RoomId>,
val currentSelection: ImmutableList<RoomId>,
val initialSelection: ImmutableList<RoomId>,
val eventSink: (ManageAuthorizedSpacesEvent) -> Unit
)

View File

@@ -9,11 +9,53 @@
package io.element.android.features.securityandprivacy.impl.manageauthorizedspaces
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.previewutils.room.aSpaceRoom
import kotlinx.collections.immutable.toImmutableList
open class ManageAuthorizedSpacesStateProvider : PreviewParameterProvider<ManageAuthorizedSpacesState> {
override val values: Sequence<ManageAuthorizedSpacesState>
get() = sequenceOf()
get() = sequenceOf(
aManageAuthorizedSpacesState(),
aManageAuthorizedSpacesState(
unknownSpaceIds = listOf(aRoomId(99))
),
aManageAuthorizedSpacesState(
currentSelection = listOf(aRoomId(1), aRoomId(3)),
initialSelection = listOf(aRoomId(1)),
),
)
}
private fun aRoomId(index: Int) = RoomId("!roomId$index:matrix.org")
private fun aSpaceRoomList(count: Int): List<SpaceRoom> {
return (1..count).map { index ->
aSpaceRoom(
roomId = aRoomId(index),
displayName = "Space $index",
canonicalAlias = if (index % 2 == 0) {
RoomAlias("#space$index:matrix.org")
} else {
null
}
)
}
}
private fun aManageAuthorizedSpacesState(
joinedSpaces: List<SpaceRoom> = aSpaceRoomList(5),
unknownSpaceIds: List<RoomId> = emptyList(),
currentSelection: List<RoomId> = emptyList(),
initialSelection: List<RoomId> = emptyList(),
eventSink: (ManageAuthorizedSpacesEvent) -> Unit = {},
) = ManageAuthorizedSpacesState(
joinedSpaces = joinedSpaces.toImmutableList(),
unknownSpaceIds = unknownSpaceIds.toImmutableList(),
currentSelection = currentSelection.toImmutableList(),
initialSelection = initialSelection.toImmutableList(),
eventSink = eventSink,
)

View File

@@ -10,17 +10,34 @@ package io.element.android.features.securityandprivacy.impl.manageauthorizedspac
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.securityandprivacy.impl.R
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.BigIcon
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.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
@@ -43,11 +60,105 @@ fun ManageAuthorizedSpacesView(
LazyColumn(
modifier = Modifier.padding(padding)
) {
headerItem()
item {
ListSectionHeader(
title = stringResource(R.string.screen_manage_authorized_spaces_your_spaces_section_title),
hasDivider = false,
)
}
items(items = state.joinedSpaces) { space ->
CheckableSpaceListItem(
headlineText = space.displayName,
supportingText = space.canonicalAlias?.value,
avatarData = space.getAvatarData(AvatarSize.SpaceMember),
checked = state.currentSelection.contains(space.roomId),
onCheckedChange = { _ ->
state.eventSink(
ManageAuthorizedSpacesEvent.ToggleSpace(space.roomId)
)
}
)
}
if(state.unknownSpaceIds.isNotEmpty()){
item {
ListSectionHeader(
title = stringResource(R.string.screen_manage_authorized_spaces_unknown_spaces_section_title),
hasDivider = false,
)
}
items(items = state.unknownSpaceIds) {
CheckableSpaceListItem(
headlineText = stringResource(R.string.screen_manage_authorized_spaces_unknown_space),
supportingText = it.value,
avatarData = null,
checked = state.currentSelection.contains(it),
onCheckedChange = { _ ->
state.eventSink(
ManageAuthorizedSpacesEvent.ToggleSpace(it)
)
}
)
}
}
}
}
}
private fun LazyListScope.headerItem() {
item(key = "header", contentType = "header") {
IconTitleSubtitleMolecule(
modifier = Modifier.padding(
vertical = 16.dp,
horizontal = 24.dp
),
title = stringResource(R.string.screen_manage_authorized_spaces_header),
subTitle = null,
iconStyle = BigIcon.Style.Default(
vectorIcon = CompoundIcons.SpaceSolid(),
)
)
}
}
@Composable
private fun CheckableSpaceListItem(
headlineText: String,
supportingText: String?,
avatarData: AvatarData?,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
ListItem(
headlineContent = {
Text(text = headlineText)
},
supportingContent = supportingText?.let {
@Composable {
Text(text = supportingText)
}
},
leadingContent = avatarData?.let{
ListItemContent.Custom {
Avatar(
avatarData = avatarData,
avatarType = AvatarType.Space(),
)
}
},
trailingContent = ListItemContent.Checkbox(
checked = checked,
enabled = enabled,
),
enabled = enabled,
onClick = { onCheckedChange(!checked) },
modifier = modifier,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ManageAuthorizedSpacesTopBar(
@@ -57,7 +168,7 @@ private fun ManageAuthorizedSpacesTopBar(
) {
TopAppBar(
modifier = modifier,
titleStr = stringResource(CommonStrings.screen_manage_authorized_spaces_title),
titleStr = stringResource(R.string.screen_manage_authorized_spaces_title),
navigationIcon = { BackButton(onClick = onBackClick) },
actions = {
TextButton(