feat(security&privacy) : use permissions and improve save

This commit is contained in:
ganfra 2025-01-23 23:29:35 +01:00
parent 75fef6b325
commit 88fce64d2f
4 changed files with 150 additions and 113 deletions

View file

@ -10,7 +10,6 @@ package io.element.android.features.roomdetails.impl.securityandprivacy
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -22,6 +21,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.matchesServer import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.matchesServer
import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -35,8 +35,10 @@ import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibilit
import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
class SecurityAndPrivacyPresenter @AssistedInject constructor( class SecurityAndPrivacyPresenter @AssistedInject constructor(
@Assisted private val navigator: SecurityAndPrivacyNavigator, @Assisted private val navigator: SecurityAndPrivacyNavigator,
@ -51,17 +53,22 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor(
@Composable @Composable
override fun present(): SecurityAndPrivacyState { override fun present(): SecurityAndPrivacyState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
val homeserverName = remember { matrixClient.userIdServerName() } val homeserverName = remember { matrixClient.userIdServerName() }
val roomInfo by room.roomInfoFlow.collectAsState(initial = null) val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val isVisibleInRoomDirectory by isRoomVisibleInRoomDirectory() val roomInfo by room.roomInfoFlow.collectAsState(null)
val savedIsVisibleInRoomDirectory = remember { mutableStateOf<AsyncData<Boolean>>(AsyncData.Uninitialized) }
IsRoomVisibleInRoomDirectoryEffect(savedIsVisibleInRoomDirectory)
val savedSettings by remember { val savedSettings by remember {
derivedStateOf { derivedStateOf {
SecurityAndPrivacySettings( SecurityAndPrivacySettings(
roomAccess = roomInfo?.joinRule.map(), roomAccess = roomInfo?.joinRule.map(),
isEncrypted = room.isEncrypted, isEncrypted = room.isEncrypted,
isVisibleInRoomDirectory = isVisibleInRoomDirectory, isVisibleInRoomDirectory = savedIsVisibleInRoomDirectory.value,
historyVisibility = roomInfo?.historyVisibility?.map(), historyVisibility = roomInfo?.historyVisibility.map(),
addressName = roomInfo?.firstDisplayableAlias(homeserverName)?.value addressName = roomInfo?.firstDisplayableAlias(homeserverName)?.value
) )
} }
@ -73,15 +80,12 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor(
var editedHistoryVisibility by remember(savedSettings.historyVisibility) { var editedHistoryVisibility by remember(savedSettings.historyVisibility) {
mutableStateOf(savedSettings.historyVisibility) mutableStateOf(savedSettings.historyVisibility)
} }
var editedVisibleInRoomDirectory by remember(savedSettings.isVisibleInRoomDirectory) {
mutableStateOf(savedSettings.isVisibleInRoomDirectory)
}
var editedIsEncrypted by remember(savedSettings.isEncrypted) { var editedIsEncrypted by remember(savedSettings.isEncrypted) {
mutableStateOf(savedSettings.isEncrypted) mutableStateOf(savedSettings.isEncrypted)
} }
var editedVisibleInRoomDirectory by remember(savedIsVisibleInRoomDirectory.value) {
var showEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) } mutableStateOf(savedIsVisibleInRoomDirectory.value)
}
val editedSettings = SecurityAndPrivacySettings( val editedSettings = SecurityAndPrivacySettings(
roomAccess = editedRoomAccess, roomAccess = editedRoomAccess,
isEncrypted = editedIsEncrypted, isEncrypted = editedIsEncrypted,
@ -90,18 +94,24 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor(
addressName = savedSettings.addressName, addressName = savedSettings.addressName,
) )
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } var showEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
val permissions by room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value)
fun handleEvents(event: SecurityAndPrivacyEvents) { fun handleEvents(event: SecurityAndPrivacyEvents) {
when (event) { when (event) {
SecurityAndPrivacyEvents.Save -> { SecurityAndPrivacyEvents.Save -> {
coroutineScope.save(saveAction, savedSettings, editedSettings) coroutineScope.save(
saveAction = saveAction,
isVisibleInRoomDirectory = savedIsVisibleInRoomDirectory,
savedSettings = savedSettings,
editedSettings = editedSettings
)
} }
is SecurityAndPrivacyEvents.ChangeRoomAccess -> { is SecurityAndPrivacyEvents.ChangeRoomAccess -> {
editedRoomAccess = event.roomAccess editedRoomAccess = event.roomAccess
} }
is SecurityAndPrivacyEvents.ToggleEncryptionState -> { is SecurityAndPrivacyEvents.ToggleEncryptionState -> {
if (editedSettings.isEncrypted) { if (editedIsEncrypted) {
editedIsEncrypted = false editedIsEncrypted = false
} else { } else {
showEncryptionConfirmation = true showEncryptionConfirmation = true
@ -133,123 +143,137 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor(
homeserverName = homeserverName, homeserverName = homeserverName,
showEncryptionConfirmation = showEncryptionConfirmation, showEncryptionConfirmation = showEncryptionConfirmation,
saveAction = saveAction.value, saveAction = saveAction.value,
permissions = permissions,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
// If the history visibility is not available for the current access, use the fallback.
LaunchedEffect(state.availableHistoryVisibilities) { LaunchedEffect(state.availableHistoryVisibilities) {
editedSettings.historyVisibility?.also { if (editedSettings.historyVisibility !in state.availableHistoryVisibilities) {
if (it !in state.availableHistoryVisibilities) { editedHistoryVisibility = editedSettings.historyVisibility.fallback()
editedHistoryVisibility = it.fallback()
}
} }
} }
return state return state
} }
@Composable @Composable
private fun isRoomVisibleInRoomDirectory(): State<AsyncData<Boolean>> { private fun IsRoomVisibleInRoomDirectoryEffect(isRoomVisible: MutableState<AsyncData<Boolean>>) {
val result = remember { mutableStateOf<AsyncData<Boolean>>(AsyncData.Uninitialized) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
result.runUpdatingState { isRoomVisible.runUpdatingState {
room.getRoomVisibility().map { it == RoomVisibility.Public } room.getRoomVisibility().map { it == RoomVisibility.Public }
} }
} }
return result
} }
private fun CoroutineScope.save( private fun CoroutineScope.save(
saveAction: MutableState<AsyncAction<Unit>>, saveAction: MutableState<AsyncAction<Unit>>,
isVisibleInRoomDirectory: MutableState<AsyncData<Boolean>>,
savedSettings: SecurityAndPrivacySettings, savedSettings: SecurityAndPrivacySettings,
editedSettings: SecurityAndPrivacySettings, editedSettings: SecurityAndPrivacySettings,
) = launch { ) = launch {
suspend { suspend {
var somethingWentWrong = false val enableEncryption = async {
if (editedSettings.isEncrypted && !savedSettings.isEncrypted) { if (editedSettings.isEncrypted && !savedSettings.isEncrypted) {
room room.enableEncryption()
.enableEncryption() } else {
.onFailure { Result.success(Unit)
Timber.d("Failed to enable encryption") }
somethingWentWrong = true
}
} }
if (editedSettings.historyVisibility != null && editedSettings.historyVisibility != savedSettings.historyVisibility) { val updateHistoryVisibility = async {
room if (editedSettings.historyVisibility != savedSettings.historyVisibility) {
.updateHistoryVisibility(editedSettings.historyVisibility.map()) room.updateHistoryVisibility(editedSettings.historyVisibility.map())
.onFailure { } else {
Timber.d("Failed to update history visibility") Result.success(Unit)
somethingWentWrong = true }
}
} }
if (editedSettings.roomAccess != savedSettings.roomAccess) { val updateJoinRule = async {
room if (editedSettings.roomAccess != savedSettings.roomAccess) {
.updateJoinRule(editedSettings.roomAccess.map()) room.updateJoinRule(editedSettings.roomAccess.map())
.onFailure { } else {
Timber.d("Failed to update join rule") Result.success(Unit)
somethingWentWrong = true }
}
} }
val updateRoomVisibility = async {
val editedIsVisibleInRoomDirectory = when (editedSettings.roomAccess) { // When a user changes join rules to something other than knock or public,
SecurityAndPrivacyRoomAccess.AskToJoin, // the room should be automatically made invisible (private) in the room directory.
SecurityAndPrivacyRoomAccess.Anyone -> editedSettings.isVisibleInRoomDirectory.dataOrNull() val editedIsVisibleInRoomDirectory = when (editedSettings.roomAccess) {
else -> false SecurityAndPrivacyRoomAccess.AskToJoin,
SecurityAndPrivacyRoomAccess.Anyone -> editedSettings.isVisibleInRoomDirectory.dataOrNull()
else -> false
}
val savedIsVisibleInRoomDirectory = savedSettings.isVisibleInRoomDirectory.dataOrNull()
if (editedIsVisibleInRoomDirectory != null && editedIsVisibleInRoomDirectory != savedIsVisibleInRoomDirectory) {
val roomVisibility = if (editedIsVisibleInRoomDirectory) RoomVisibility.Public else RoomVisibility.Private
room
.updateRoomVisibility(roomVisibility)
.onSuccess {
isVisibleInRoomDirectory.value = AsyncData.Success(editedIsVisibleInRoomDirectory)
}
} else {
Result.success(Unit)
}
} }
val savedIsVisibleInRoomDirectory = savedSettings.isVisibleInRoomDirectory.dataOrNull() val artificialDelay = async {
if (editedIsVisibleInRoomDirectory != null && editedIsVisibleInRoomDirectory != savedIsVisibleInRoomDirectory) { // Artificial delay to make sure the user sees the loading state
val roomVisibility = if (editedIsVisibleInRoomDirectory) RoomVisibility.Public else RoomVisibility.Private delay(500)
room Result.success(Unit)
.updateRoomVisibility(roomVisibility)
.onFailure {
Timber.d("Failed to update room visibility")
somethingWentWrong = true
}
} }
if (somethingWentWrong) { val results = awaitAll(
error("") enableEncryption,
updateHistoryVisibility,
updateJoinRule,
updateRoomVisibility,
artificialDelay
)
if (results.any { it.isFailure }) {
throw SecurityAndPrivacyFailures.SaveFailed
} }
}.runCatchingUpdatingState(saveAction) }.runCatchingUpdatingState(saveAction)
} }
}
private fun JoinRule?.map(): SecurityAndPrivacyRoomAccess { private fun JoinRule?.map(): SecurityAndPrivacyRoomAccess {
return when (this) { return when (this) {
JoinRule.Public -> SecurityAndPrivacyRoomAccess.Anyone JoinRule.Public -> SecurityAndPrivacyRoomAccess.Anyone
JoinRule.Knock, is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoin JoinRule.Knock, is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoin
is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember
is JoinRule.Custom, is JoinRule.Custom,
JoinRule.Invite, JoinRule.Invite,
JoinRule.Private, JoinRule.Private,
null -> SecurityAndPrivacyRoomAccess.InviteOnly null -> SecurityAndPrivacyRoomAccess.InviteOnly
}
}
private fun SecurityAndPrivacyRoomAccess.map(): JoinRule {
return when (this) {
SecurityAndPrivacyRoomAccess.Anyone -> JoinRule.Public
SecurityAndPrivacyRoomAccess.AskToJoin -> JoinRule.Knock
SecurityAndPrivacyRoomAccess.InviteOnly -> JoinRule.Private
SecurityAndPrivacyRoomAccess.SpaceMember -> error("Unsupported")
}
}
private fun RoomHistoryVisibility.map(): SecurityAndPrivacyHistoryVisibility {
return when (this) {
RoomHistoryVisibility.Joined,
RoomHistoryVisibility.Invited -> SecurityAndPrivacyHistoryVisibility.SinceInvite
RoomHistoryVisibility.Shared,
is RoomHistoryVisibility.Custom -> SecurityAndPrivacyHistoryVisibility.SinceSelection
RoomHistoryVisibility.WorldReadable -> SecurityAndPrivacyHistoryVisibility.Anyone
}
}
private fun SecurityAndPrivacyHistoryVisibility.map(): RoomHistoryVisibility {
return when (this) {
SecurityAndPrivacyHistoryVisibility.SinceSelection -> RoomHistoryVisibility.Shared
SecurityAndPrivacyHistoryVisibility.SinceInvite -> RoomHistoryVisibility.Invited
SecurityAndPrivacyHistoryVisibility.Anyone -> RoomHistoryVisibility.WorldReadable
}
}
private fun MatrixRoomInfo.firstDisplayableAlias(serverName: String): RoomAlias? {
return aliases.firstOrNull { it.matchesServer(serverName) } ?: aliases.firstOrNull()
} }
} }
private fun SecurityAndPrivacyRoomAccess.map(): JoinRule {
return when (this) {
SecurityAndPrivacyRoomAccess.Anyone -> JoinRule.Public
SecurityAndPrivacyRoomAccess.AskToJoin -> JoinRule.Knock
SecurityAndPrivacyRoomAccess.InviteOnly -> JoinRule.Private
SecurityAndPrivacyRoomAccess.SpaceMember -> error("Unsupported")
}
}
private fun RoomHistoryVisibility?.map(): SecurityAndPrivacyHistoryVisibility {
return when (this) {
RoomHistoryVisibility.WorldReadable -> SecurityAndPrivacyHistoryVisibility.Anyone
RoomHistoryVisibility.Joined,
RoomHistoryVisibility.Invited -> SecurityAndPrivacyHistoryVisibility.SinceInvite
RoomHistoryVisibility.Shared,
is RoomHistoryVisibility.Custom,
null -> SecurityAndPrivacyHistoryVisibility.SinceSelection
}
}
private fun SecurityAndPrivacyHistoryVisibility.map(): RoomHistoryVisibility {
return when (this) {
SecurityAndPrivacyHistoryVisibility.SinceSelection -> RoomHistoryVisibility.Shared
SecurityAndPrivacyHistoryVisibility.SinceInvite -> RoomHistoryVisibility.Invited
SecurityAndPrivacyHistoryVisibility.Anyone -> RoomHistoryVisibility.WorldReadable
}
}
private fun MatrixRoomInfo.firstDisplayableAlias(serverName: String): RoomAlias? {
return aliases.firstOrNull { it.matchesServer(serverName) } ?: aliases.firstOrNull()
}

View file

@ -7,6 +7,7 @@
package io.element.android.features.roomdetails.impl.securityandprivacy package io.element.android.features.roomdetails.impl.securityandprivacy
import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.SecurityAndPrivacyPermissions
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
@ -18,12 +19,12 @@ data class SecurityAndPrivacyState(
val homeserverName: String, val homeserverName: String,
val showEncryptionConfirmation: Boolean, val showEncryptionConfirmation: Boolean,
val saveAction: AsyncAction<Unit>, val saveAction: AsyncAction<Unit>,
private val permissions: SecurityAndPrivacyPermissions,
val eventSink: (SecurityAndPrivacyEvents) -> Unit val eventSink: (SecurityAndPrivacyEvents) -> Unit
) { ) {
val canBeSaved = savedSettings != editedSettings val canBeSaved = savedSettings != editedSettings
val availableHistoryVisibilities = buildSet { val availableHistoryVisibilities = buildSet {
add(SecurityAndPrivacyHistoryVisibility.SinceSelection) add(SecurityAndPrivacyHistoryVisibility.SinceSelection)
if (editedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone && !editedSettings.isEncrypted) { if (editedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone && !editedSettings.isEncrypted) {
@ -32,16 +33,17 @@ data class SecurityAndPrivacyState(
add(SecurityAndPrivacyHistoryVisibility.SinceInvite) add(SecurityAndPrivacyHistoryVisibility.SinceInvite)
} }
} }
val showRoomAccessSection: Boolean = true
val showRoomVisibilitySections = editedSettings.roomAccess != SecurityAndPrivacyRoomAccess.InviteOnly val showRoomAccessSection = permissions.canChangeRoomAccess
val showHistoryVisibilitySection = editedSettings.historyVisibility != null val showRoomVisibilitySections = permissions.canChangeRoomVisibility && editedSettings.roomAccess != SecurityAndPrivacyRoomAccess.InviteOnly
val showEncryptionSection = true val showHistoryVisibilitySection = permissions.canChangeHistoryVisibility
val showEncryptionSection = permissions.canChangeEncryption
} }
data class SecurityAndPrivacySettings( data class SecurityAndPrivacySettings(
val roomAccess: SecurityAndPrivacyRoomAccess, val roomAccess: SecurityAndPrivacyRoomAccess,
val isEncrypted: Boolean, val isEncrypted: Boolean,
val historyVisibility: SecurityAndPrivacyHistoryVisibility?, val historyVisibility: SecurityAndPrivacyHistoryVisibility,
val addressName: String?, val addressName: String?,
val isVisibleInRoomDirectory: AsyncData<Boolean> val isVisibleInRoomDirectory: AsyncData<Boolean>
) )
@ -54,8 +56,8 @@ enum class SecurityAndPrivacyHistoryVisibility {
*/ */
fun fallback(): SecurityAndPrivacyHistoryVisibility { fun fallback(): SecurityAndPrivacyHistoryVisibility {
return when (this) { return when (this) {
SinceSelection -> SinceSelection SinceSelection,
SinceInvite -> Anyone SinceInvite -> SinceSelection
Anyone -> SinceInvite Anyone -> SinceInvite
} }
} }
@ -64,3 +66,7 @@ enum class SecurityAndPrivacyHistoryVisibility {
enum class SecurityAndPrivacyRoomAccess { enum class SecurityAndPrivacyRoomAccess {
InviteOnly, AskToJoin, Anyone, SpaceMember InviteOnly, AskToJoin, Anyone, SpaceMember
} }
sealed class SecurityAndPrivacyFailures : Exception() {
data object SaveFailed : SecurityAndPrivacyFailures()
}

View file

@ -8,6 +8,7 @@
package io.element.android.features.roomdetails.impl.securityandprivacy package io.element.android.features.roomdetails.impl.securityandprivacy
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.SecurityAndPrivacyPermissions
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
@ -27,7 +28,7 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAn
) )
), ),
aSecurityAndPrivacyState( aSecurityAndPrivacyState(
editedSettings = aSecurityAndPrivacySettings( savedSettings = aSecurityAndPrivacySettings(
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember
) )
), ),
@ -51,7 +52,7 @@ fun aSecurityAndPrivacySettings(
roomAccess: SecurityAndPrivacyRoomAccess = SecurityAndPrivacyRoomAccess.InviteOnly, roomAccess: SecurityAndPrivacyRoomAccess = SecurityAndPrivacyRoomAccess.InviteOnly,
isEncrypted: Boolean = true, isEncrypted: Boolean = true,
formattedAddress: String? = null, formattedAddress: String? = null,
historyVisibility: SecurityAndPrivacyHistoryVisibility? = null, historyVisibility: SecurityAndPrivacyHistoryVisibility = SecurityAndPrivacyHistoryVisibility.SinceSelection,
isVisibleInRoomDirectory: AsyncData<Boolean> = AsyncData.Uninitialized, isVisibleInRoomDirectory: AsyncData<Boolean> = AsyncData.Uninitialized,
) = SecurityAndPrivacySettings( ) = SecurityAndPrivacySettings(
roomAccess = roomAccess, roomAccess = roomAccess,
@ -67,6 +68,12 @@ fun aSecurityAndPrivacyState(
homeserverName: String = "myserver.xyz", homeserverName: String = "myserver.xyz",
showEncryptionConfirmation: Boolean = false, showEncryptionConfirmation: Boolean = false,
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized, saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
permissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions(
canChangeRoomAccess = true,
canChangeHistoryVisibility = true,
canChangeEncryption = true,
canChangeRoomVisibility = true
),
eventSink: (SecurityAndPrivacyEvents) -> Unit = {} eventSink: (SecurityAndPrivacyEvents) -> Unit = {}
) = SecurityAndPrivacyState( ) = SecurityAndPrivacyState(
editedSettings = editedSettings, editedSettings = editedSettings,
@ -74,5 +81,6 @@ fun aSecurityAndPrivacyState(
homeserverName = homeserverName, homeserverName = homeserverName,
showEncryptionConfirmation = showEncryptionConfirmation, showEncryptionConfirmation = showEncryptionConfirmation,
saveAction = saveAction, saveAction = saveAction,
permissions = permissions,
eventSink = eventSink eventSink = eventSink
) )

View file

@ -256,7 +256,6 @@ private fun RoomAddressSection(
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_address_section_footer)) }, supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_address_section_footer)) },
onClick = onRoomAddressClick, onClick = onRoomAddressClick,
colors = ListItemDefaults.colors(trailingIconColor = ElementTheme.colors.iconAccentPrimary), colors = ListItemDefaults.colors(trailingIconColor = ElementTheme.colors.iconAccentPrimary),
alwaysClickable = true
) )
ListItem( ListItem(