feature(security&privacy): start ManageAuthorizedSpacesView
This commit is contained in:
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user