feat(security&privacy) : manage save action and some edge cases.
This commit is contained in:
parent
9ee5927489
commit
be199e25ff
5 changed files with 320 additions and 185 deletions
|
|
@ -16,4 +16,5 @@ sealed interface SecurityAndPrivacyEvents {
|
||||||
data object ConfirmEnableEncryption: SecurityAndPrivacyEvents
|
data object ConfirmEnableEncryption: SecurityAndPrivacyEvents
|
||||||
data class ChangeHistoryVisibility(val historyVisibility: SecurityAndPrivacyHistoryVisibility) : SecurityAndPrivacyEvents
|
data class ChangeHistoryVisibility(val historyVisibility: SecurityAndPrivacyHistoryVisibility) : SecurityAndPrivacyEvents
|
||||||
data class ChangeRoomVisibility(val isVisibleInRoomDirectory: Boolean) : SecurityAndPrivacyEvents
|
data class ChangeRoomVisibility(val isVisibleInRoomDirectory: Boolean) : SecurityAndPrivacyEvents
|
||||||
|
data object DismissSaveError : SecurityAndPrivacyEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,25 +8,35 @@
|
||||||
package io.element.android.features.roomdetails.impl.securityandprivacy
|
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.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
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
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.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
|
||||||
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
|
import io.element.android.libraries.architecture.runUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||||
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
|
||||||
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
import io.element.android.libraries.matrix.api.room.join.JoinRule
|
||||||
import java.util.Optional
|
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
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,
|
||||||
|
|
@ -40,67 +50,68 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): SecurityAndPrivacyState {
|
override fun present(): SecurityAndPrivacyState {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val homeserverName = remember { matrixClient.userIdServerName() }
|
val homeserverName = remember { matrixClient.userIdServerName() }
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState(initial = null)
|
val roomInfo by room.roomInfoFlow.collectAsState(initial = null)
|
||||||
|
val isVisibleInRoomDirectory by isRoomVisibleInRoomDirectory()
|
||||||
val isVisibleInRoomDirectory = remember {
|
|
||||||
mutableStateOf<AsyncData<Boolean>>(AsyncData.Uninitialized)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = Optional.ofNullable(isVisibleInRoomDirectory.value),
|
isVisibleInRoomDirectory = isVisibleInRoomDirectory,
|
||||||
historyVisibility = Optional.ofNullable(roomInfo?.historyVisibility?.map()),
|
historyVisibility = roomInfo?.historyVisibility?.map(),
|
||||||
addressName = Optional.ofNullable(roomInfo?.firstDisplayableAlias(homeserverName)?.value),
|
addressName = roomInfo?.firstDisplayableAlias(homeserverName)?.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentRoomAccess by remember(savedSettings.roomAccess) {
|
var editedRoomAccess by remember(savedSettings.roomAccess) {
|
||||||
mutableStateOf(savedSettings.roomAccess)
|
mutableStateOf(savedSettings.roomAccess)
|
||||||
}
|
}
|
||||||
var currentHistoryVisibility by remember(savedSettings.historyVisibility) {
|
var editedHistoryVisibility by remember(savedSettings.historyVisibility) {
|
||||||
mutableStateOf(savedSettings.historyVisibility)
|
mutableStateOf(savedSettings.historyVisibility)
|
||||||
}
|
}
|
||||||
var currentVisibleInRoomDirectory by remember(savedSettings.isVisibleInRoomDirectory) {
|
var editedVisibleInRoomDirectory by remember(savedSettings.isVisibleInRoomDirectory) {
|
||||||
mutableStateOf(savedSettings.isVisibleInRoomDirectory)
|
mutableStateOf(savedSettings.isVisibleInRoomDirectory)
|
||||||
}
|
}
|
||||||
var currentIsEncrypted by remember(savedSettings.isEncrypted) {
|
var editedIsEncrypted by remember(savedSettings.isEncrypted) {
|
||||||
mutableStateOf(savedSettings.isEncrypted)
|
mutableStateOf(savedSettings.isEncrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
var showEncryptionConfirmation by remember { mutableStateOf(false) }
|
var showEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) }
|
||||||
|
|
||||||
val currentSettings = SecurityAndPrivacySettings(
|
val editedSettings = SecurityAndPrivacySettings(
|
||||||
roomAccess = currentRoomAccess,
|
roomAccess = editedRoomAccess,
|
||||||
isEncrypted = currentIsEncrypted,
|
isEncrypted = editedIsEncrypted,
|
||||||
isVisibleInRoomDirectory = currentVisibleInRoomDirectory,
|
isVisibleInRoomDirectory = editedVisibleInRoomDirectory,
|
||||||
historyVisibility = currentHistoryVisibility,
|
historyVisibility = editedHistoryVisibility,
|
||||||
addressName = savedSettings.addressName,
|
addressName = savedSettings.addressName,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
fun handleEvents(event: SecurityAndPrivacyEvents) {
|
fun handleEvents(event: SecurityAndPrivacyEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
SecurityAndPrivacyEvents.Save -> {
|
SecurityAndPrivacyEvents.Save -> {
|
||||||
|
coroutineScope.save(saveAction, savedSettings, editedSettings)
|
||||||
}
|
}
|
||||||
is SecurityAndPrivacyEvents.ChangeRoomAccess -> {
|
is SecurityAndPrivacyEvents.ChangeRoomAccess -> {
|
||||||
currentRoomAccess = event.roomAccess
|
editedRoomAccess = event.roomAccess
|
||||||
}
|
}
|
||||||
is SecurityAndPrivacyEvents.ToggleEncryptionState -> {
|
is SecurityAndPrivacyEvents.ToggleEncryptionState -> {
|
||||||
if(currentSettings.isEncrypted) {
|
if (editedSettings.isEncrypted) {
|
||||||
currentIsEncrypted = false
|
editedIsEncrypted = false
|
||||||
} else {
|
} else {
|
||||||
showEncryptionConfirmation = true
|
showEncryptionConfirmation = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SecurityAndPrivacyEvents.ChangeHistoryVisibility -> {
|
is SecurityAndPrivacyEvents.ChangeHistoryVisibility -> {
|
||||||
currentHistoryVisibility = Optional.of(event.historyVisibility)
|
editedHistoryVisibility = event.historyVisibility
|
||||||
}
|
}
|
||||||
is SecurityAndPrivacyEvents.ChangeRoomVisibility -> {
|
is SecurityAndPrivacyEvents.ChangeRoomVisibility -> {
|
||||||
currentVisibleInRoomDirectory = Optional.of(AsyncData.Success(event.isVisibleInRoomDirectory))
|
editedVisibleInRoomDirectory = AsyncData.Success(event.isVisibleInRoomDirectory)
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvents.EditRoomAddress -> navigator.openEditRoomAddress()
|
SecurityAndPrivacyEvents.EditRoomAddress -> navigator.openEditRoomAddress()
|
||||||
SecurityAndPrivacyEvents.CancelEnableEncryption -> {
|
SecurityAndPrivacyEvents.CancelEnableEncryption -> {
|
||||||
|
|
@ -108,60 +119,137 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvents.ConfirmEnableEncryption -> {
|
SecurityAndPrivacyEvents.ConfirmEnableEncryption -> {
|
||||||
showEncryptionConfirmation = false
|
showEncryptionConfirmation = false
|
||||||
currentIsEncrypted = true
|
editedIsEncrypted = true
|
||||||
|
}
|
||||||
|
SecurityAndPrivacyEvents.DismissSaveError -> {
|
||||||
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SecurityAndPrivacyState(
|
|
||||||
|
val state = SecurityAndPrivacyState(
|
||||||
savedSettings = savedSettings,
|
savedSettings = savedSettings,
|
||||||
currentSettings = currentSettings,
|
editedSettings = editedSettings,
|
||||||
homeserverName = homeserverName,
|
homeserverName = homeserverName,
|
||||||
showEncryptionConfirmation = showEncryptionConfirmation,
|
showEncryptionConfirmation = showEncryptionConfirmation,
|
||||||
|
saveAction = saveAction.value,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
|
LaunchedEffect(state.availableHistoryVisibilities) {
|
||||||
|
editedSettings.historyVisibility?.also {
|
||||||
|
if (it !in state.availableHistoryVisibilities) {
|
||||||
|
editedHistoryVisibility = it.fallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun isRoomVisibleInRoomDirectory(): State<AsyncData<Boolean>> {
|
||||||
|
val result = remember { mutableStateOf<AsyncData<Boolean>>(AsyncData.Uninitialized) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
result.runUpdatingState {
|
||||||
|
room.getRoomVisibility().map { it == RoomVisibility.Public }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CoroutineScope.save(
|
||||||
|
saveAction: MutableState<AsyncAction<Unit>>,
|
||||||
|
savedSettings: SecurityAndPrivacySettings,
|
||||||
|
editedSettings: SecurityAndPrivacySettings,
|
||||||
|
) = launch {
|
||||||
|
suspend {
|
||||||
|
var somethingWentWrong = false
|
||||||
|
if (editedSettings.isEncrypted && !savedSettings.isEncrypted) {
|
||||||
|
room
|
||||||
|
.enableEncryption()
|
||||||
|
.onFailure {
|
||||||
|
Timber.d("Failed to enable encryption")
|
||||||
|
somethingWentWrong = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (editedSettings.historyVisibility != null && editedSettings.historyVisibility != savedSettings.historyVisibility) {
|
||||||
|
room
|
||||||
|
.updateHistoryVisibility(editedSettings.historyVisibility.map())
|
||||||
|
.onFailure {
|
||||||
|
Timber.d("Failed to update history visibility")
|
||||||
|
somethingWentWrong = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (editedSettings.roomAccess != savedSettings.roomAccess) {
|
||||||
|
room
|
||||||
|
.updateJoinRule(editedSettings.roomAccess.map())
|
||||||
|
.onFailure {
|
||||||
|
Timber.d("Failed to update join rule")
|
||||||
|
somethingWentWrong = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val editedIsVisibleInRoomDirectory = when (editedSettings.roomAccess) {
|
||||||
|
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)
|
||||||
|
.onFailure {
|
||||||
|
Timber.d("Failed to update room visibility")
|
||||||
|
somethingWentWrong = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (somethingWentWrong) {
|
||||||
|
error("")
|
||||||
|
}
|
||||||
|
}.runCatchingUpdatingState(saveAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Custom,
|
||||||
|
JoinRule.Invite,
|
||||||
|
JoinRule.Private,
|
||||||
|
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 JoinRule?.map(): SecurityAndPrivacyRoomAccess {
|
|
||||||
return when (this) {
|
|
||||||
JoinRule.Public -> SecurityAndPrivacyRoomAccess.Anyone
|
|
||||||
JoinRule.Knock, is JoinRule.KnockRestricted -> SecurityAndPrivacyRoomAccess.AskToJoin
|
|
||||||
is JoinRule.Restricted -> SecurityAndPrivacyRoomAccess.SpaceMember
|
|
||||||
is JoinRule.Custom,
|
|
||||||
JoinRule.Invite,
|
|
||||||
JoinRule.Private,
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,46 +7,58 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.securityandprivacy
|
package io.element.android.features.roomdetails.impl.securityandprivacy
|
||||||
|
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import java.util.Optional
|
|
||||||
import kotlin.jvm.optionals.getOrNull
|
|
||||||
|
|
||||||
data class SecurityAndPrivacyState(
|
data class SecurityAndPrivacyState(
|
||||||
|
// the settings that are currently applied on the room.
|
||||||
val savedSettings: SecurityAndPrivacySettings,
|
val savedSettings: SecurityAndPrivacySettings,
|
||||||
val currentSettings: SecurityAndPrivacySettings,
|
// the settings the user wants to apply.
|
||||||
|
val editedSettings: SecurityAndPrivacySettings,
|
||||||
val homeserverName: String,
|
val homeserverName: String,
|
||||||
val showEncryptionConfirmation: Boolean,
|
val showEncryptionConfirmation: Boolean,
|
||||||
|
val saveAction: AsyncAction<Unit>,
|
||||||
val eventSink: (SecurityAndPrivacyEvents) -> Unit
|
val eventSink: (SecurityAndPrivacyEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val canBeSaved = savedSettings != currentSettings
|
val canBeSaved = savedSettings != editedSettings
|
||||||
|
|
||||||
val showRoomVisibilitySections = currentSettings.roomAccess != SecurityAndPrivacyRoomAccess.InviteOnly && currentSettings.historyVisibility.isPresent
|
|
||||||
|
|
||||||
val availableHistoryVisibilities = buildSet {
|
val availableHistoryVisibilities = buildSet {
|
||||||
add(SecurityAndPrivacyHistoryVisibility.SinceSelection)
|
add(SecurityAndPrivacyHistoryVisibility.SinceSelection)
|
||||||
if (currentSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone && !currentSettings.isEncrypted) {
|
if (editedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone && !editedSettings.isEncrypted) {
|
||||||
add(SecurityAndPrivacyHistoryVisibility.Anyone)
|
add(SecurityAndPrivacyHistoryVisibility.Anyone)
|
||||||
} else {
|
} else {
|
||||||
add(SecurityAndPrivacyHistoryVisibility.SinceInvite)
|
add(SecurityAndPrivacyHistoryVisibility.SinceInvite)
|
||||||
}
|
}
|
||||||
if (savedSettings.historyVisibility.getOrNull() == SecurityAndPrivacyHistoryVisibility.SinceInvite) {
|
|
||||||
add(SecurityAndPrivacyHistoryVisibility.SinceInvite)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val showRoomHistoryVisibilitySection = availableHistoryVisibilities.isNotEmpty() && currentSettings.historyVisibility.isPresent
|
val showRoomAccessSection: Boolean = true
|
||||||
|
val showRoomVisibilitySections = editedSettings.roomAccess != SecurityAndPrivacyRoomAccess.InviteOnly
|
||||||
|
val showHistoryVisibilitySection = editedSettings.historyVisibility != null
|
||||||
|
val showEncryptionSection = true
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SecurityAndPrivacySettings(
|
data class SecurityAndPrivacySettings(
|
||||||
val roomAccess: SecurityAndPrivacyRoomAccess,
|
val roomAccess: SecurityAndPrivacyRoomAccess,
|
||||||
val isEncrypted: Boolean,
|
val isEncrypted: Boolean,
|
||||||
val historyVisibility: Optional<SecurityAndPrivacyHistoryVisibility>,
|
val historyVisibility: SecurityAndPrivacyHistoryVisibility?,
|
||||||
val addressName: Optional<String>,
|
val addressName: String?,
|
||||||
val isVisibleInRoomDirectory: Optional<AsyncData<Boolean>>
|
val isVisibleInRoomDirectory: AsyncData<Boolean>
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class SecurityAndPrivacyHistoryVisibility {
|
enum class SecurityAndPrivacyHistoryVisibility {
|
||||||
SinceSelection, SinceInvite, Anyone
|
SinceSelection, SinceInvite, Anyone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the fallback visibility when the current visibility is not available.
|
||||||
|
*/
|
||||||
|
fun fallback(): SecurityAndPrivacyHistoryVisibility {
|
||||||
|
return when (this) {
|
||||||
|
SinceSelection -> SinceSelection
|
||||||
|
SinceInvite -> Anyone
|
||||||
|
Anyone -> SinceInvite
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class SecurityAndPrivacyRoomAccess {
|
enum class SecurityAndPrivacyRoomAccess {
|
||||||
|
|
|
||||||
|
|
@ -8,37 +8,37 @@
|
||||||
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.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import java.util.Optional
|
|
||||||
|
|
||||||
open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAndPrivacyState> {
|
open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAndPrivacyState> {
|
||||||
override val values: Sequence<SecurityAndPrivacyState>
|
override val values: Sequence<SecurityAndPrivacyState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aSecurityAndPrivacyState(),
|
aSecurityAndPrivacyState(),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
currentSettings = aSecurityAndPrivacySettings(
|
editedSettings = aSecurityAndPrivacySettings(
|
||||||
roomAccess = SecurityAndPrivacyRoomAccess.AskToJoin
|
roomAccess = SecurityAndPrivacyRoomAccess.AskToJoin
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
currentSettings = aSecurityAndPrivacySettings(
|
editedSettings = aSecurityAndPrivacySettings(
|
||||||
roomAccess = SecurityAndPrivacyRoomAccess.Anyone,
|
roomAccess = SecurityAndPrivacyRoomAccess.Anyone,
|
||||||
isEncrypted = false,
|
isEncrypted = false,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
currentSettings = aSecurityAndPrivacySettings(
|
editedSettings = aSecurityAndPrivacySettings(
|
||||||
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember
|
roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
currentSettings = aSecurityAndPrivacySettings(
|
editedSettings = aSecurityAndPrivacySettings(
|
||||||
isVisibleInRoomDirectory = Optional.of(AsyncData.Loading())
|
isVisibleInRoomDirectory = AsyncData.Loading()
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
currentSettings = aSecurityAndPrivacySettings(
|
editedSettings = aSecurityAndPrivacySettings(
|
||||||
isVisibleInRoomDirectory = Optional.of(AsyncData.Success(true))
|
isVisibleInRoomDirectory = AsyncData.Success(true)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
|
|
@ -50,9 +50,9 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAn
|
||||||
fun aSecurityAndPrivacySettings(
|
fun aSecurityAndPrivacySettings(
|
||||||
roomAccess: SecurityAndPrivacyRoomAccess = SecurityAndPrivacyRoomAccess.InviteOnly,
|
roomAccess: SecurityAndPrivacyRoomAccess = SecurityAndPrivacyRoomAccess.InviteOnly,
|
||||||
isEncrypted: Boolean = true,
|
isEncrypted: Boolean = true,
|
||||||
formattedAddress: Optional<String> = Optional.empty(),
|
formattedAddress: String? = null,
|
||||||
historyVisibility: Optional<SecurityAndPrivacyHistoryVisibility> = Optional.of(SecurityAndPrivacyHistoryVisibility.SinceSelection),
|
historyVisibility: SecurityAndPrivacyHistoryVisibility? = null,
|
||||||
isVisibleInRoomDirectory: Optional<AsyncData<Boolean>> = Optional.empty()
|
isVisibleInRoomDirectory: AsyncData<Boolean> = AsyncData.Uninitialized,
|
||||||
) = SecurityAndPrivacySettings(
|
) = SecurityAndPrivacySettings(
|
||||||
roomAccess = roomAccess,
|
roomAccess = roomAccess,
|
||||||
isEncrypted = isEncrypted,
|
isEncrypted = isEncrypted,
|
||||||
|
|
@ -62,15 +62,17 @@ fun aSecurityAndPrivacySettings(
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aSecurityAndPrivacyState(
|
fun aSecurityAndPrivacyState(
|
||||||
currentSettings: SecurityAndPrivacySettings = aSecurityAndPrivacySettings(),
|
savedSettings: SecurityAndPrivacySettings = aSecurityAndPrivacySettings(),
|
||||||
savedSettings: SecurityAndPrivacySettings = currentSettings,
|
editedSettings: SecurityAndPrivacySettings = savedSettings,
|
||||||
homeserverName: String = "myserver.xyz",
|
homeserverName: String = "myserver.xyz",
|
||||||
showEncryptionConfirmation: Boolean = false,
|
showEncryptionConfirmation: Boolean = false,
|
||||||
|
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
eventSink: (SecurityAndPrivacyEvents) -> Unit = {}
|
eventSink: (SecurityAndPrivacyEvents) -> Unit = {}
|
||||||
) = SecurityAndPrivacyState(
|
) = SecurityAndPrivacyState(
|
||||||
currentSettings = currentSettings,
|
editedSettings = editedSettings,
|
||||||
savedSettings = savedSettings,
|
savedSettings = savedSettings,
|
||||||
homeserverName = homeserverName,
|
homeserverName = homeserverName,
|
||||||
showEncryptionConfirmation = showEncryptionConfirmation,
|
showEncryptionConfirmation = showEncryptionConfirmation,
|
||||||
|
saveAction = saveAction,
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
|
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
|
|
@ -47,8 +49,6 @@ 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.TextButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import java.util.Optional
|
|
||||||
import kotlin.jvm.optionals.getOrNull
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SecurityAndPrivacyView(
|
fun SecurityAndPrivacyView(
|
||||||
|
|
@ -76,40 +76,58 @@ fun SecurityAndPrivacyView(
|
||||||
.consumeWindowInsets(padding),
|
.consumeWindowInsets(padding),
|
||||||
verticalArrangement = Arrangement.spacedBy(32.dp),
|
verticalArrangement = Arrangement.spacedBy(32.dp),
|
||||||
) {
|
) {
|
||||||
RoomAccessSection(
|
if (state.showRoomAccessSection) {
|
||||||
modifier = Modifier.padding(top = 24.dp),
|
RoomAccessSection(
|
||||||
selected = state.currentSettings.roomAccess,
|
modifier = Modifier.padding(top = 24.dp),
|
||||||
onSelected = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) },
|
edited = state.editedSettings.roomAccess,
|
||||||
)
|
saved = state.savedSettings.roomAccess,
|
||||||
|
onSelected = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
if (state.showRoomVisibilitySections) {
|
if (state.showRoomVisibilitySections) {
|
||||||
RoomVisibilitySection(state.homeserverName)
|
RoomVisibilitySection(state.homeserverName)
|
||||||
RoomAddressSection(
|
RoomAddressSection(
|
||||||
roomAddress = state.currentSettings.addressName,
|
roomAddress = state.editedSettings.addressName,
|
||||||
homeserverName = state.homeserverName,
|
homeserverName = state.homeserverName,
|
||||||
onRoomAddressClick = { state.eventSink(SecurityAndPrivacyEvents.EditRoomAddress) },
|
onRoomAddressClick = { state.eventSink(SecurityAndPrivacyEvents.EditRoomAddress) },
|
||||||
isVisibleInPublicDirectory = state.currentSettings.isVisibleInRoomDirectory,
|
isVisibleInRoomDirectory = state.editedSettings.isVisibleInRoomDirectory,
|
||||||
onVisibilityChange = { isVisible ->
|
onVisibilityChange = { isVisible ->
|
||||||
state.eventSink(SecurityAndPrivacyEvents.ChangeRoomVisibility(isVisible))
|
state.eventSink(SecurityAndPrivacyEvents.ChangeRoomVisibility(isVisible))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
EncryptionSection(
|
if (state.showEncryptionSection) {
|
||||||
isRoomEncrypted = state.currentSettings.isEncrypted,
|
EncryptionSection(
|
||||||
isSectionEnabled = !state.savedSettings.isEncrypted,
|
isRoomEncrypted = state.editedSettings.isEncrypted,
|
||||||
onToggleEncryption = { state.eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) },
|
canToggleEncryption = !state.savedSettings.isEncrypted,
|
||||||
showConfirmation = state.showEncryptionConfirmation,
|
onToggleEncryption = { state.eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) },
|
||||||
onDismissConfirmation = { state.eventSink(SecurityAndPrivacyEvents.CancelEnableEncryption) },
|
showConfirmation = state.showEncryptionConfirmation,
|
||||||
onConfirmEncryption = { state.eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) },
|
onDismissConfirmation = { state.eventSink(SecurityAndPrivacyEvents.CancelEnableEncryption) },
|
||||||
)
|
onConfirmEncryption = { state.eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) },
|
||||||
if (state.showRoomHistoryVisibilitySection) {
|
)
|
||||||
RoomHistorySection(
|
}
|
||||||
selectedOption = state.currentSettings.historyVisibility.get(),
|
if (state.showHistoryVisibilitySection) {
|
||||||
|
HistoryVisibilitySection(
|
||||||
|
editedOption = state.editedSettings.historyVisibility,
|
||||||
|
savedOptions = state.savedSettings.historyVisibility,
|
||||||
availableOptions = state.availableHistoryVisibilities,
|
availableOptions = state.availableHistoryVisibilities,
|
||||||
onSelected = { state.eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(it)) },
|
onSelected = { state.eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AsyncActionView(
|
||||||
|
async = state.saveAction,
|
||||||
|
onSuccess = { },
|
||||||
|
onErrorDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) },
|
||||||
|
errorMessage = { stringResource(CommonStrings.error_unknown) },
|
||||||
|
progressDialog = {
|
||||||
|
AsyncActionViewDefaults.ProgressDialog(
|
||||||
|
progressText = stringResource(CommonStrings.common_saving),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onRetry = { state.eventSink(SecurityAndPrivacyEvents.Save) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -160,7 +178,8 @@ private fun SecurityAndPrivacySection(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomAccessSection(
|
private fun RoomAccessSection(
|
||||||
selected: SecurityAndPrivacyRoomAccess,
|
edited: SecurityAndPrivacyRoomAccess,
|
||||||
|
saved: SecurityAndPrivacyRoomAccess,
|
||||||
onSelected: (SecurityAndPrivacyRoomAccess) -> Unit,
|
onSelected: (SecurityAndPrivacyRoomAccess) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -171,26 +190,27 @@ private fun RoomAccessSection(
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_invite_only_option_title)) },
|
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_invite_only_option_title)) },
|
||||||
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_invite_only_option_description)) },
|
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_invite_only_option_description)) },
|
||||||
trailingContent = ListItemContent.RadioButton(selected = selected == SecurityAndPrivacyRoomAccess.InviteOnly),
|
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly),
|
||||||
onClick = { onSelected(SecurityAndPrivacyRoomAccess.InviteOnly) },
|
onClick = { onSelected(SecurityAndPrivacyRoomAccess.InviteOnly) },
|
||||||
)
|
)
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_ask_to_join_option_title)) },
|
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_ask_to_join_option_title)) },
|
||||||
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_ask_to_join_option_description)) },
|
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_ask_to_join_option_description)) },
|
||||||
trailingContent = ListItemContent.RadioButton(selected = selected == SecurityAndPrivacyRoomAccess.AskToJoin),
|
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin),
|
||||||
onClick = { onSelected(SecurityAndPrivacyRoomAccess.AskToJoin) },
|
onClick = { onSelected(SecurityAndPrivacyRoomAccess.AskToJoin) },
|
||||||
)
|
)
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_anyone_option_title)) },
|
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_anyone_option_title)) },
|
||||||
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_anyone_option_description)) },
|
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_anyone_option_description)) },
|
||||||
trailingContent = ListItemContent.RadioButton(selected = selected == SecurityAndPrivacyRoomAccess.Anyone),
|
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.Anyone),
|
||||||
onClick = { onSelected(SecurityAndPrivacyRoomAccess.Anyone) },
|
onClick = { onSelected(SecurityAndPrivacyRoomAccess.Anyone) },
|
||||||
)
|
)
|
||||||
if (selected == SecurityAndPrivacyRoomAccess.SpaceMember) {
|
if (saved == SecurityAndPrivacyRoomAccess.SpaceMember) {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_space_members_option_title)) },
|
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_space_members_option_title)) },
|
||||||
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_space_members_option_description)) },
|
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_access_space_members_option_description)) },
|
||||||
trailingContent = ListItemContent.RadioButton(selected = true, enabled = false),
|
trailingContent = ListItemContent.RadioButton(selected = true, enabled = false),
|
||||||
|
enabled = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -217,9 +237,9 @@ private fun RoomVisibilitySection(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomAddressSection(
|
private fun RoomAddressSection(
|
||||||
roomAddress: Optional<String>,
|
roomAddress: String?,
|
||||||
homeserverName: String,
|
homeserverName: String,
|
||||||
isVisibleInPublicDirectory: Optional<AsyncData<Boolean>>,
|
isVisibleInRoomDirectory: AsyncData<Boolean>,
|
||||||
onRoomAddressClick: () -> Unit,
|
onRoomAddressClick: () -> Unit,
|
||||||
onVisibilityChange: (Boolean) -> Unit,
|
onVisibilityChange: (Boolean) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
@ -230,54 +250,53 @@ private fun RoomAddressSection(
|
||||||
) {
|
) {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(text = roomAddress.getOrNull() ?: stringResource(CommonStrings.screen_security_and_privacy_add_room_address_action))
|
Text(text = roomAddress ?: stringResource(CommonStrings.screen_security_and_privacy_add_room_address_action))
|
||||||
},
|
},
|
||||||
trailingContent = if (roomAddress.isEmpty) ListItemContent.Icon(IconSource.Vector(CompoundIcons.Plus())) else null,
|
trailingContent = if (roomAddress.isNullOrEmpty()) ListItemContent.Icon(IconSource.Vector(CompoundIcons.Plus())) else null,
|
||||||
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
|
alwaysClickable = true
|
||||||
)
|
)
|
||||||
if (isVisibleInPublicDirectory.isPresent) {
|
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_directory_visibility_toggle_title)) },
|
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_directory_visibility_toggle_title)) },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_directory_visibility_section_footer, homeserverName))
|
Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_directory_visibility_section_footer, homeserverName))
|
||||||
},
|
},
|
||||||
trailingContent =
|
trailingContent =
|
||||||
when (val isVisible = isVisibleInPublicDirectory.get()) {
|
when (isVisibleInRoomDirectory) {
|
||||||
is AsyncData.Uninitialized, is AsyncData.Loading -> {
|
is AsyncData.Uninitialized, is AsyncData.Loading -> {
|
||||||
ListItemContent.Custom {
|
ListItemContent.Custom {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.progressSemantics()
|
.progressSemantics()
|
||||||
.size(20.dp),
|
.size(20.dp),
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AsyncData.Failure -> {
|
|
||||||
ListItemContent.Switch(
|
|
||||||
checked = false,
|
|
||||||
enabled = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is AsyncData.Success -> {
|
|
||||||
ListItemContent.Switch(
|
|
||||||
checked = isVisible.data,
|
|
||||||
onChange = onVisibilityChange
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
is AsyncData.Failure -> {
|
||||||
}
|
ListItemContent.Switch(
|
||||||
|
checked = false,
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is AsyncData.Success -> {
|
||||||
|
ListItemContent.Switch(
|
||||||
|
checked = isVisibleInRoomDirectory.data,
|
||||||
|
onChange = onVisibilityChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun EncryptionSection(
|
private fun EncryptionSection(
|
||||||
isRoomEncrypted: Boolean,
|
isRoomEncrypted: Boolean,
|
||||||
isSectionEnabled: Boolean,
|
canToggleEncryption: Boolean,
|
||||||
showConfirmation: Boolean,
|
showConfirmation: Boolean,
|
||||||
onToggleEncryption: () -> Unit,
|
onToggleEncryption: () -> Unit,
|
||||||
onConfirmEncryption: () -> Unit,
|
onConfirmEncryption: () -> Unit,
|
||||||
|
|
@ -293,10 +312,10 @@ private fun EncryptionSection(
|
||||||
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_encryption_section_footer)) },
|
supportingContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_encryption_section_footer)) },
|
||||||
trailingContent = ListItemContent.Switch(
|
trailingContent = ListItemContent.Switch(
|
||||||
checked = isRoomEncrypted,
|
checked = isRoomEncrypted,
|
||||||
enabled = isSectionEnabled,
|
enabled = canToggleEncryption,
|
||||||
onChange = { onToggleEncryption() },
|
onChange = { onToggleEncryption() },
|
||||||
),
|
),
|
||||||
onClick = onToggleEncryption,
|
onClick = if (canToggleEncryption) onToggleEncryption else null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (showConfirmation) {
|
if (showConfirmation) {
|
||||||
|
|
@ -311,8 +330,9 @@ private fun EncryptionSection(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RoomHistorySection(
|
private fun HistoryVisibilitySection(
|
||||||
selectedOption: SecurityAndPrivacyHistoryVisibility,
|
editedOption: SecurityAndPrivacyHistoryVisibility?,
|
||||||
|
savedOptions: SecurityAndPrivacyHistoryVisibility?,
|
||||||
availableOptions: Set<SecurityAndPrivacyHistoryVisibility>,
|
availableOptions: Set<SecurityAndPrivacyHistoryVisibility>,
|
||||||
onSelected: (SecurityAndPrivacyHistoryVisibility) -> Unit,
|
onSelected: (SecurityAndPrivacyHistoryVisibility) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|
@ -323,34 +343,46 @@ private fun RoomHistorySection(
|
||||||
) {
|
) {
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
for (availableOption in availableOptions) {
|
for (availableOption in availableOptions) {
|
||||||
val isSelected = availableOption == selectedOption
|
val isSelected = availableOption == editedOption
|
||||||
when (availableOption) {
|
HistoryVisibilityItem(
|
||||||
SecurityAndPrivacyHistoryVisibility.SinceSelection -> {
|
option = availableOption,
|
||||||
ListItem(
|
isSelected = isSelected,
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_history_since_selecting_option_title)) },
|
onSelected = onSelected,
|
||||||
trailingContent = ListItemContent.RadioButton(selected = isSelected),
|
)
|
||||||
onClick = { onSelected(availableOption) },
|
}
|
||||||
)
|
if (savedOptions != null && !availableOptions.contains(savedOptions)) {
|
||||||
}
|
HistoryVisibilityItem(
|
||||||
SecurityAndPrivacyHistoryVisibility.SinceInvite -> {
|
option = savedOptions,
|
||||||
ListItem(
|
isSelected = true,
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_history_since_invite_option_title)) },
|
isEnabled = false,
|
||||||
trailingContent = ListItemContent.RadioButton(selected = isSelected),
|
onSelected = {},
|
||||||
onClick = { onSelected(availableOption) },
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
SecurityAndPrivacyHistoryVisibility.Anyone -> {
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(text = stringResource(CommonStrings.screen_security_and_privacy_room_history_anyone_option_title)) },
|
|
||||||
trailingContent = ListItemContent.RadioButton(selected = isSelected),
|
|
||||||
onClick = { onSelected(availableOption) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun HistoryVisibilityItem(
|
||||||
|
option: SecurityAndPrivacyHistoryVisibility,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onSelected: (SecurityAndPrivacyHistoryVisibility) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isEnabled: Boolean = true,
|
||||||
|
) {
|
||||||
|
val headlineText = when (option) {
|
||||||
|
SecurityAndPrivacyHistoryVisibility.SinceSelection -> stringResource(CommonStrings.screen_security_and_privacy_room_history_since_selecting_option_title)
|
||||||
|
SecurityAndPrivacyHistoryVisibility.SinceInvite -> stringResource(CommonStrings.screen_security_and_privacy_room_history_since_invite_option_title)
|
||||||
|
SecurityAndPrivacyHistoryVisibility.Anyone -> stringResource(CommonStrings.screen_security_and_privacy_room_history_anyone_option_title)
|
||||||
|
}
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(text = headlineText) },
|
||||||
|
trailingContent = ListItemContent.RadioButton(selected = isSelected, enabled = isEnabled),
|
||||||
|
onClick = { onSelected(option) },
|
||||||
|
enabled = isEnabled,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewWithLargeHeight
|
@PreviewWithLargeHeight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SecurityAndPrivacyViewLightPreview(@PreviewParameter(SecurityAndPrivacyStateProvider::class) state: SecurityAndPrivacyState) =
|
internal fun SecurityAndPrivacyViewLightPreview(@PreviewParameter(SecurityAndPrivacyStateProvider::class) state: SecurityAndPrivacyState) =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue