feature(security&privacy): start branching logic of ManageAuthorizedSpaces
This commit is contained in:
@@ -24,6 +24,7 @@ import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint
|
||||
import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions
|
||||
import io.element.android.features.securityandprivacy.impl.editroomaddress.EditRoomAddressNode
|
||||
import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.ManageAuthorizedSpacesNode
|
||||
import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyNode
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
@@ -58,6 +59,9 @@ class SecurityAndPrivacyFlowNode(
|
||||
|
||||
@Parcelize
|
||||
data object EditRoomAddress : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object ManageAuthorizedSpaces : NavTarget
|
||||
}
|
||||
|
||||
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
||||
@@ -89,6 +93,9 @@ class SecurityAndPrivacyFlowNode(
|
||||
NavTarget.EditRoomAddress -> {
|
||||
createNode<EditRoomAddressNode>(buildContext, plugins = listOf(navigator))
|
||||
}
|
||||
NavTarget.ManageAuthorizedSpaces -> {
|
||||
createNode<ManageAuthorizedSpacesNode>(buildContext, plugins = listOf(navigator))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ interface SecurityAndPrivacyNavigator : Plugin {
|
||||
fun onDone()
|
||||
fun openEditRoomAddress()
|
||||
fun closeEditRoomAddress()
|
||||
fun openManageAuthorizedSpaces()
|
||||
fun closeManageAuthorizedSpaces()
|
||||
}
|
||||
|
||||
class BackstackSecurityAndPrivacyNavigator(
|
||||
@@ -35,4 +37,12 @@ class BackstackSecurityAndPrivacyNavigator(
|
||||
override fun closeEditRoomAddress() {
|
||||
backStack.pop()
|
||||
}
|
||||
|
||||
override fun openManageAuthorizedSpaces() {
|
||||
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.ManageAuthorizedSpaces)
|
||||
}
|
||||
|
||||
override fun closeManageAuthorizedSpaces() {
|
||||
backStack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ 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
|
||||
|
||||
@@ -80,11 +79,11 @@ fun ManageAuthorizedSpacesView(
|
||||
}
|
||||
)
|
||||
}
|
||||
if(state.unknownSpaceIds.isNotEmpty()){
|
||||
if (state.unknownSpaceIds.isNotEmpty()) {
|
||||
item {
|
||||
ListSectionHeader(
|
||||
title = stringResource(R.string.screen_manage_authorized_spaces_unknown_spaces_section_title),
|
||||
hasDivider = false,
|
||||
hasDivider = true,
|
||||
)
|
||||
}
|
||||
items(items = state.unknownSpaceIds) {
|
||||
@@ -140,7 +139,7 @@ private fun CheckableSpaceListItem(
|
||||
Text(text = supportingText)
|
||||
}
|
||||
},
|
||||
leadingContent = avatarData?.let{
|
||||
leadingContent = avatarData?.let {
|
||||
ListItemContent.Custom {
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
@@ -158,7 +157,6 @@ private fun CheckableSpaceListItem(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ManageAuthorizedSpacesTopBar(
|
||||
|
||||
@@ -10,10 +10,13 @@ package io.element.android.features.securityandprivacy.impl.root
|
||||
|
||||
sealed interface SecurityAndPrivacyEvent {
|
||||
data object EditRoomAddress : SecurityAndPrivacyEvent
|
||||
data object ManageAuthorizedSpaces : SecurityAndPrivacyEvent
|
||||
data object Save : SecurityAndPrivacyEvent
|
||||
data object Exit : SecurityAndPrivacyEvent
|
||||
data object DismissExitConfirmation : SecurityAndPrivacyEvent
|
||||
data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvent
|
||||
// Special case for "Space Members"
|
||||
data object SelectSpaceMemberAccess : SecurityAndPrivacyEvent
|
||||
data object ToggleEncryptionState : SecurityAndPrivacyEvent
|
||||
data object CancelEnableEncryption : SecurityAndPrivacyEvent
|
||||
data object ConfirmEnableEncryption : SecurityAndPrivacyEvent
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -37,9 +38,15 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||
import io.element.android.libraries.matrix.api.room.join.AllowRule
|
||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState
|
||||
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -86,7 +93,7 @@ class SecurityAndPrivacyPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
var editedRoomAccess by remember(savedSettings.roomAccess) {
|
||||
val editedRoomAccess = remember(savedSettings.roomAccess) {
|
||||
mutableStateOf(savedSettings.roomAccess)
|
||||
}
|
||||
var editedHistoryVisibility by remember(savedSettings.historyVisibility) {
|
||||
@@ -99,13 +106,26 @@ class SecurityAndPrivacyPresenter(
|
||||
mutableStateOf(savedIsVisibleInRoomDirectory.value)
|
||||
}
|
||||
val editedSettings = SecurityAndPrivacySettings(
|
||||
roomAccess = editedRoomAccess,
|
||||
roomAccess = editedRoomAccess.value,
|
||||
isEncrypted = editedIsEncrypted,
|
||||
isVisibleInRoomDirectory = editedVisibleInRoomDirectory,
|
||||
historyVisibility = editedHistoryVisibility,
|
||||
address = savedSettings.address,
|
||||
)
|
||||
|
||||
val selectableJoinedSpaces by produceState(persistentSetOf()) {
|
||||
val joinedParentSpaces = matrixClient
|
||||
.spaceService
|
||||
.joinedParents(room.roomId)
|
||||
.getOrDefault(emptyList())
|
||||
|
||||
val nonParentJoinedSpaces = savedSettings.roomAccess
|
||||
.spaceIds()
|
||||
.mapNotNull { spaceId -> matrixClient.spaceService.getSpaceRoom(spaceId) }
|
||||
|
||||
value = (joinedParentSpaces + nonParentJoinedSpaces).toImmutableSet()
|
||||
}
|
||||
|
||||
var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
|
||||
val permissions by room.permissionsAsState(SecurityAndPrivacyPermissions.DEFAULT) { perms ->
|
||||
perms.securityAndPrivacyPermissions()
|
||||
@@ -122,7 +142,7 @@ class SecurityAndPrivacyPresenter(
|
||||
)
|
||||
}
|
||||
is SecurityAndPrivacyEvent.ChangeRoomAccess -> {
|
||||
editedRoomAccess = event.roomAccess
|
||||
editedRoomAccess.value = event.roomAccess
|
||||
}
|
||||
is SecurityAndPrivacyEvent.ToggleEncryptionState -> {
|
||||
if (editedIsEncrypted) {
|
||||
@@ -161,6 +181,12 @@ class SecurityAndPrivacyPresenter(
|
||||
SecurityAndPrivacyEvent.DismissExitConfirmation -> {
|
||||
saveAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
SecurityAndPrivacyEvent.ManageAuthorizedSpaces -> navigator.openManageAuthorizedSpaces()
|
||||
SecurityAndPrivacyEvent.SelectSpaceMemberAccess -> handleSpaceMemberAccessSelection(
|
||||
selectableJoinedSpaces = selectableJoinedSpaces,
|
||||
savedAccess = savedSettings.roomAccess,
|
||||
editedAccess = editedRoomAccess,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,13 +205,14 @@ class SecurityAndPrivacyPresenter(
|
||||
saveAction = saveAction.value,
|
||||
permissions = permissions,
|
||||
isSpace = roomInfo.isSpace,
|
||||
selectableJoinedSpaces = selectableJoinedSpaces,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
|
||||
// Revert changes that the user is not allowed to make anymore
|
||||
LaunchedEffect(permissions, state.editedSettings.roomAccess) {
|
||||
if (!state.showRoomAccessSection) {
|
||||
editedRoomAccess = savedSettings.roomAccess
|
||||
editedRoomAccess.value = savedSettings.roomAccess
|
||||
}
|
||||
if (!state.showEncryptionSection) {
|
||||
editedIsEncrypted = savedSettings.isEncrypted
|
||||
@@ -202,6 +229,51 @@ class SecurityAndPrivacyPresenter(
|
||||
return state
|
||||
}
|
||||
|
||||
private fun handleSpaceMemberAccessSelection(
|
||||
selectableJoinedSpaces: Set<SpaceRoom>,
|
||||
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||
editedAccess: MutableState<SecurityAndPrivacyRoomAccess>,
|
||||
) {
|
||||
if(editedAccess.value is SecurityAndPrivacyRoomAccess.SpaceMember) {
|
||||
return
|
||||
}
|
||||
val spaceSelection = getSpaceSelection(selectableJoinedSpaces, savedAccess)
|
||||
when(spaceSelection){
|
||||
is SpaceSelection.None -> Unit
|
||||
is SpaceSelection.Multiple -> navigator.openManageAuthorizedSpaces()
|
||||
is SpaceSelection.Single -> {
|
||||
val newRoomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||
spaceIds = persistentListOf(spaceSelection.spaceId)
|
||||
)
|
||||
editedAccess.value = newRoomAccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSpaceSelection(
|
||||
selectableJoinedSpaces: Set<SpaceRoom>,
|
||||
savedAccess: SecurityAndPrivacyRoomAccess,
|
||||
): SpaceSelection {
|
||||
val selectableSpacesCount = (selectableJoinedSpaces.map { it.roomId } + savedAccess.spaceIds()).toSet().size
|
||||
return when {
|
||||
selectableSpacesCount == 0 -> SpaceSelection.None
|
||||
selectableSpacesCount > 1 -> SpaceSelection.Multiple
|
||||
else -> {
|
||||
val joinedSpace = selectableJoinedSpaces.firstOrNull()
|
||||
if (joinedSpace != null) {
|
||||
SpaceSelection.Single(joinedSpace.roomId, joinedSpace)
|
||||
} else {
|
||||
val spaceId = savedAccess.spaceIds().firstOrNull()
|
||||
if (spaceId == null) {
|
||||
SpaceSelection.None
|
||||
} else {
|
||||
SpaceSelection.Single(spaceId, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.isRoomVisibleInRoomDirectory(isRoomVisible: MutableState<AsyncData<Boolean>>) = launch {
|
||||
isRoomVisible.runUpdatingState {
|
||||
room.getRoomVisibility().map { it == RoomVisibility.Public }
|
||||
@@ -280,7 +352,12 @@ private fun JoinRule?.map(): SecurityAndPrivacyRoomAccess {
|
||||
return when (this) {
|
||||
JoinRule.Public -> SecurityAndPrivacyRoomAccess.Anyone
|
||||
JoinRule.Knock, is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoin
|
||||
is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember
|
||||
is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember(
|
||||
spaceIds = this.rules
|
||||
.filterIsInstance<AllowRule.RoomMembership>()
|
||||
.map { it.roomId }
|
||||
.toImmutableList()
|
||||
)
|
||||
JoinRule.Invite -> SecurityAndPrivacyRoomAccess.InviteOnly
|
||||
// All other cases are not supported so we default to InviteOnly
|
||||
is JoinRule.Custom,
|
||||
@@ -294,8 +371,9 @@ private fun SecurityAndPrivacyRoomAccess.map(): JoinRule? {
|
||||
SecurityAndPrivacyRoomAccess.Anyone -> JoinRule.Public
|
||||
SecurityAndPrivacyRoomAccess.AskToJoin -> JoinRule.Knock
|
||||
SecurityAndPrivacyRoomAccess.InviteOnly -> JoinRule.Private
|
||||
// SpaceMember can't be selected in the ui
|
||||
SecurityAndPrivacyRoomAccess.SpaceMember -> null
|
||||
is SecurityAndPrivacyRoomAccess.SpaceMember -> JoinRule.Restricted(
|
||||
rules = this.spaceIds.map { AllowRule.RoomMembership(it) }.toImmutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ package io.element.android.features.securityandprivacy.impl.root
|
||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
data class SecurityAndPrivacyState(
|
||||
@@ -24,8 +29,10 @@ data class SecurityAndPrivacyState(
|
||||
val saveAction: AsyncAction<Unit>,
|
||||
val isSpace: Boolean,
|
||||
private val permissions: SecurityAndPrivacyPermissions,
|
||||
private val selectableJoinedSpaces: ImmutableSet<SpaceRoom>,
|
||||
val eventSink: (SecurityAndPrivacyEvent) -> Unit
|
||||
) {
|
||||
|
||||
val canBeSaved = savedSettings != editedSettings
|
||||
|
||||
// Logic is in https://github.com/element-hq/element-meta/issues/3029
|
||||
@@ -76,18 +83,31 @@ enum class SecurityAndPrivacyHistoryVisibility {
|
||||
}
|
||||
}
|
||||
|
||||
enum class SecurityAndPrivacyRoomAccess {
|
||||
InviteOnly,
|
||||
AskToJoin,
|
||||
Anyone,
|
||||
SpaceMember;
|
||||
sealed interface SpaceSelection {
|
||||
data object None : SpaceSelection
|
||||
data class Single(val spaceId: RoomId, val spaceRoom: SpaceRoom?) : SpaceSelection
|
||||
data object Multiple : SpaceSelection
|
||||
}
|
||||
|
||||
sealed interface SecurityAndPrivacyRoomAccess {
|
||||
data object InviteOnly : SecurityAndPrivacyRoomAccess
|
||||
data object AskToJoin : SecurityAndPrivacyRoomAccess
|
||||
data object Anyone : SecurityAndPrivacyRoomAccess
|
||||
data class SpaceMember(val spaceIds: ImmutableList<RoomId>) : SecurityAndPrivacyRoomAccess
|
||||
|
||||
fun canConfigureRoomVisibility(): Boolean {
|
||||
return when (this) {
|
||||
InviteOnly, SpaceMember -> false
|
||||
InviteOnly, is SpaceMember -> false
|
||||
AskToJoin, Anyone -> true
|
||||
}
|
||||
}
|
||||
|
||||
fun spaceIds(): ImmutableList<RoomId> {
|
||||
return when (this) {
|
||||
is SpaceMember -> spaceIds
|
||||
else -> persistentListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SecurityAndPrivacyFailures : Exception() {
|
||||
|
||||
@@ -12,6 +12,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
|
||||
open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAndPrivacyState> {
|
||||
override val values: Sequence<SecurityAndPrivacyState>
|
||||
@@ -61,7 +65,7 @@ private fun commonSecurityAndPrivacyStates(isSpace: Boolean): Sequence<SecurityA
|
||||
),
|
||||
aSecurityAndPrivacyState(
|
||||
savedSettings = aSecurityAndPrivacySettings(
|
||||
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember
|
||||
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember(persistentListOf())
|
||||
),
|
||||
isSpace = isSpace,
|
||||
isKnockEnabled = false,
|
||||
@@ -117,6 +121,7 @@ fun aSecurityAndPrivacyState(
|
||||
),
|
||||
isKnockEnabled: Boolean = true,
|
||||
isSpace: Boolean = false,
|
||||
selectableJoinedSpaces: Set<SpaceRoom> = emptySet(),
|
||||
eventSink: (SecurityAndPrivacyEvent) -> Unit = {}
|
||||
) = SecurityAndPrivacyState(
|
||||
editedSettings = editedSettings,
|
||||
@@ -127,5 +132,6 @@ fun aSecurityAndPrivacyState(
|
||||
isKnockEnabled = isKnockEnabled,
|
||||
permissions = permissions,
|
||||
isSpace = isSpace,
|
||||
selectableJoinedSpaces = selectableJoinedSpaces.toImmutableSet(),
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
@@ -50,6 +50,7 @@ import io.element.android.libraries.designsystem.text.stringWithLink
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
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.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
|
||||
@@ -95,6 +96,8 @@ fun SecurityAndPrivacyView(
|
||||
saved = state.savedSettings.roomAccess,
|
||||
isKnockEnabled = state.isKnockEnabled,
|
||||
onSelectOption = { state.eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(it)) },
|
||||
onManageSpacesClick = { state.eventSink(SecurityAndPrivacyEvent.ManageAuthorizedSpaces) },
|
||||
onSpaceMemberAccessClick = { state.eventSink(SecurityAndPrivacyEvent.SelectSpaceMemberAccess) }
|
||||
)
|
||||
}
|
||||
if (state.showRoomVisibilitySections) {
|
||||
@@ -212,6 +215,8 @@ private fun RoomAccessSection(
|
||||
saved: SecurityAndPrivacyRoomAccess,
|
||||
isKnockEnabled: Boolean,
|
||||
onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit,
|
||||
onSpaceMemberAccessClick: () -> Unit,
|
||||
onManageSpacesClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SecurityAndPrivacySection(
|
||||
@@ -226,17 +231,15 @@ private fun RoomAccessSection(
|
||||
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.Anyone) },
|
||||
)
|
||||
// Show space member option, but disabled as we don't support this option for now.
|
||||
if (saved == SecurityAndPrivacyRoomAccess.SpaceMember) {
|
||||
ListItem(
|
||||
headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_title)) },
|
||||
supportingContent = {
|
||||
Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_unavailable_description))
|
||||
},
|
||||
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.SpaceMember, enabled = false),
|
||||
trailingContent = ListItemContent.RadioButton(selected = edited is SecurityAndPrivacyRoomAccess.SpaceMember),
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Space())),
|
||||
enabled = false,
|
||||
onClick = onSpaceMemberAccessClick,
|
||||
)
|
||||
}
|
||||
// Show Ask to join option in two cases:
|
||||
// - the Knock FF is enabled
|
||||
// - AskToJoin is the current saved value
|
||||
@@ -257,6 +260,19 @@ private fun RoomAccessSection(
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())),
|
||||
onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) },
|
||||
)
|
||||
if (edited is SecurityAndPrivacyRoomAccess.SpaceMember) {
|
||||
val footerText = stringWithLink(
|
||||
textRes = R.string.screen_security_and_privacy_room_access_footer,
|
||||
url = stringResource(R.string.screen_security_and_privacy_room_access_footer_manage_spaces_action),
|
||||
onLinkClick = { onManageSpacesClick()},
|
||||
)
|
||||
Text(
|
||||
text = footerText,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
modifier = Modifier.padding(bottom = 12.dp, start = 56.dp, end = 24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user