Skip to content

Commit

Permalink
Merge pull request #4212 from element-hq/feature/fga/room_settings_se…
Browse files Browse the repository at this point in the history
…curity_privacy

Feature : room settings - security and privacy
  • Loading branch information
ganfra authored Jan 29, 2025
2 parents 6dca2bf + c10da01 commit 346e364
Show file tree
Hide file tree
Showing 125 changed files with 3,387 additions and 347 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import im.vector.app.features.analytics.plan.CreatedRoom
import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.features.createroom.impl.CreateRoomDataStore
Expand All @@ -31,23 +30,22 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
import io.element.android.libraries.matrix.api.roomAliasFromName
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEffect
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.Optional
import javax.inject.Inject
import kotlin.jvm.optionals.getOrNull
import kotlin.jvm.optionals.getOrDefault

class ConfigureRoomPresenter @Inject constructor(
private val dataStore: CreateRoomDataStore,
Expand Down Expand Up @@ -96,7 +94,12 @@ class ConfigureRoomPresenter @Inject constructor(
}
}

RoomAddressValidityEffect(createRoomConfig.roomVisibility.roomAddress()) { newRoomAddressValidity ->
RoomAddressValidityEffect(
client = matrixClient,
roomAliasHelper = roomAliasHelper,
newRoomAddress = createRoomConfig.roomVisibility.roomAddress().getOrDefault(""),
knownRoomAddress = null,
) { newRoomAddressValidity ->
roomAddressValidity.value = newRoomAddressValidity
}

Expand Down Expand Up @@ -146,39 +149,6 @@ class ConfigureRoomPresenter @Inject constructor(
)
}

@Composable
private fun RoomAddressValidityEffect(
roomAddress: Optional<String>,
onRoomAddressValidityChange: (RoomAddressValidity) -> Unit,
) {
val onChange by rememberUpdatedState(onRoomAddressValidityChange)
LaunchedEffect(roomAddress) {
val roomAliasName = roomAddress.getOrNull().orEmpty()
if (roomAliasName.isEmpty()) {
onChange(RoomAddressValidity.Unknown)
return@LaunchedEffect
}
// debounce the room address validation
delay(300)
val roomAlias = matrixClient.roomAliasFromName(roomAliasName).getOrNull()
if (roomAlias == null || !roomAliasHelper.isRoomAliasValid(roomAlias)) {
onChange(RoomAddressValidity.InvalidSymbols)
} else {
matrixClient.resolveRoomAlias(roomAlias)
.onSuccess { resolved ->
if (resolved.isPresent) {
onChange(RoomAddressValidity.NotAvailable)
} else {
onChange(RoomAddressValidity.Valid)
}
}
.onFailure {
onChange(RoomAddressValidity.Valid)
}
}
}
}

private fun CoroutineScope.createRoom(
config: CreateRoomConfig,
createRoomAction: MutableState<AsyncAction<RoomId>>
Expand All @@ -191,7 +161,7 @@ class ConfigureRoomPresenter @Inject constructor(
topic = config.topic,
isEncrypted = false,
isDirect = false,
visibility = RoomVisibility.PUBLIC,
visibility = RoomVisibility.Public,
joinRuleOverride = config.roomVisibility.roomAccess.toJoinRule(),
preset = RoomPreset.PUBLIC_CHAT,
invite = config.invites.map { it.userId },
Expand All @@ -204,7 +174,7 @@ class ConfigureRoomPresenter @Inject constructor(
topic = config.topic,
isEncrypted = config.roomVisibility is RoomVisibilityState.Private,
isDirect = false,
visibility = RoomVisibility.PRIVATE,
visibility = RoomVisibility.Private,
preset = RoomPreset.PRIVATE_CHAT,
invite = config.invites.map { it.userId },
avatar = avatarUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.element.android.features.createroom.impl.CreateRoomConfig
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.permissions.api.PermissionsState
import kotlinx.collections.immutable.ImmutableList

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.permissions.api.PermissionsState
import io.element.android.libraries.permissions.api.aPermissionsState
import kotlinx.collections.immutable.toImmutableList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
Expand Down Expand Up @@ -58,6 +57,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
import io.element.android.libraries.matrix.ui.room.address.RoomAddressField
import io.element.android.libraries.permissions.api.PermissionsView
import io.element.android.libraries.ui.strings.CommonStrings

Expand Down Expand Up @@ -142,10 +142,12 @@ fun ConfigureRoomView(
)
RoomAddressField(
modifier = Modifier.padding(horizontal = 16.dp),
address = state.config.roomVisibility.roomAddress,
address = state.config.roomVisibility.roomAddress.value,
homeserverName = state.homeserverName,
addressValidity = state.roomAddressValidity,
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
label = stringResource(R.string.screen_create_room_room_address_section_title),
supportingText = stringResource(R.string.screen_create_room_room_address_section_footer),
)
Spacer(Modifier)
}
Expand Down Expand Up @@ -318,47 +320,6 @@ private fun RoomAccessOptions(
}
}

@Composable
private fun RoomAddressField(
address: RoomAddress,
homeserverName: String,
addressValidity: RoomAddressValidity,
onAddressChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
TextField(
modifier = modifier.fillMaxWidth(),
value = address.value,
label = stringResource(R.string.screen_create_room_room_address_section_title),
leadingIcon = {
Text(
text = "#",
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textSecondary,
)
},
trailingIcon = {
Text(
text = homeserverName,
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textSecondary,
)
},
supportingText = when (addressValidity) {
RoomAddressValidity.InvalidSymbols -> {
stringResource(CommonStrings.error_room_address_invalid_symbols)
}
RoomAddressValidity.NotAvailable -> {
stringResource(CommonStrings.error_room_address_already_exists)
}
else -> stringResource(R.string.screen_create_room_room_address_section_footer)
},
isError = addressValidity.isError(),
onValueChange = onAddressChange,
singleLine = true,
)
}

@PreviewWithLargeHeight
@Composable
internal fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@

package io.element.android.features.createroom.impl.configureroom

import io.element.android.libraries.matrix.api.createroom.JoinRuleOverride
import io.element.android.libraries.matrix.api.room.join.JoinRule

enum class RoomAccess {
Anyone,
Knocking
}

fun RoomAccess.toJoinRule(): JoinRuleOverride {
fun RoomAccess.toJoinRule(): JoinRule? {
return when (this) {
RoomAccess.Anyone -> JoinRuleOverride.None
RoomAccess.Knocking -> JoinRuleOverride.Knock
RoomAccess.Anyone -> null
RoomAccess.Knocking -> JoinRule.Knock
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediapickers.test.FakePickerProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ class IdentityChangeStatePresenterTest {

@Test
fun `present - when the clear room emits identity change, the presenter does not emit new state`() = runTest {
val room = FakeMatrixRoom(isEncrypted = false)
val room = FakeMatrixRoom(
isEncrypted = false,
enableEncryptionResult = { Result.success(Unit) }
)
val presenter = createIdentityChangeStatePresenter(room)
presenter.test {
val initialState = awaitItem()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import io.element.android.features.roomdetails.impl.members.RoomMemberListNode
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode
import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
import io.element.android.libraries.architecture.BackstackWithOverlayBox
import io.element.android.libraries.architecture.BaseFlowNode
Expand Down Expand Up @@ -114,6 +115,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(

@Parcelize
data object KnockRequestsList : NavTarget

@Parcelize
data object SecurityAndPrivacy : NavTarget
}

override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
Expand Down Expand Up @@ -160,6 +164,10 @@ class RoomDetailsFlowNode @AssistedInject constructor(
backstack.push(NavTarget.KnockRequestsList)
}

override fun openSecurityAndPrivacy() {
backstack.push(NavTarget.SecurityAndPrivacy)
}

override fun onJoinCall() {
val inputs = CallType.RoomCall(
sessionId = room.sessionId,
Expand Down Expand Up @@ -290,6 +298,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
NavTarget.KnockRequestsList -> {
knockRequestsListEntryPoint.createNode(this, buildContext)
}
NavTarget.SecurityAndPrivacy -> {
createNode<SecurityAndPrivacyFlowNode>(buildContext)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class RoomDetailsNode @AssistedInject constructor(
fun openAdminSettings()
fun openPinnedMessagesList()
fun openKnockRequestsList()
fun openSecurityAndPrivacy()
fun onJoinCall()
}

Expand Down Expand Up @@ -121,6 +122,10 @@ class RoomDetailsNode @AssistedInject constructor(
callbacks.forEach { it.openKnockRequestsList() }
}

private fun openSecurityAndPrivacy() {
callbacks.forEach { it.openSecurityAndPrivacy() }
}

@Composable
override fun View(modifier: Modifier) {
val context = LocalContext.current
Expand Down Expand Up @@ -153,6 +158,7 @@ class RoomDetailsNode @AssistedInject constructor(
onJoinCallClick = ::onJoinCall,
onPinnedMessagesClick = ::openPinnedMessages,
onKnockRequestsClick = ::openKnockRequestsLists,
onSecurityAndPrivacyClick = ::openSecurityAndPrivacy
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package io.element.android.features.roomdetails.impl

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
Expand All @@ -24,6 +23,7 @@ import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
Expand Down Expand Up @@ -104,7 +104,7 @@ class RoomDetailsPresenter @Inject constructor(
val dmMember by room.getDirectRoomMember(membersState)
val currentMember by room.getCurrentRoomMember(membersState)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType by getRoomType(dmMember, currentMember)
val roomType = getRoomType(dmMember, currentMember)
val roomCallState = roomCallStatePresenter.present()

val topicState = remember(canEditTopic, roomTopic, roomType) {
Expand Down Expand Up @@ -147,10 +147,17 @@ class RoomDetailsPresenter @Inject constructor(

val roomMemberDetailsState = roomMemberDetailsPresenter?.present()

val securityAndPrivacyPermissions = room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value)
val canShowSecurityAndPrivacy by remember {
derivedStateOf {
isKnockRequestsEnabled && roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny
}
}

return RoomDetailsState(
roomId = room.roomId,
roomName = roomName,
roomAlias = room.alias,
roomAlias = room.canonicalAlias,
roomAvatarUrl = roomAvatar,
roomTopic = topicState,
memberCount = room.joinedMemberCount,
Expand All @@ -172,6 +179,7 @@ class RoomDetailsPresenter @Inject constructor(
pinnedMessagesCount = pinnedMessagesCount,
canShowKnockRequests = canShowKnockRequests,
knockRequestsCount = knockRequestsCount,
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
eventSink = ::handleEvents,
)
}
Expand All @@ -187,16 +195,14 @@ class RoomDetailsPresenter @Inject constructor(
private fun getRoomType(
dmMember: RoomMember?,
currentMember: RoomMember?,
): State<RoomDetailsType> = remember(dmMember, currentMember) {
derivedStateOf {
if (dmMember != null && currentMember != null) {
RoomDetailsType.Dm(
me = currentMember,
otherMember = dmMember,
)
} else {
RoomDetailsType.Room
}
): RoomDetailsType = remember(dmMember, currentMember) {
if (dmMember != null && currentMember != null) {
RoomDetailsType.Dm(
me = currentMember,
otherMember = dmMember,
)
} else {
RoomDetailsType.Room
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ data class RoomDetailsState(
val pinnedMessagesCount: Int?,
val canShowKnockRequests: Boolean,
val knockRequestsCount: Int?,
val canShowSecurityAndPrivacy: Boolean,
val eventSink: (RoomDetailsEvent) -> Unit
) {
val roomBadges = buildList {
Expand Down
Loading

0 comments on commit 346e364

Please sign in to comment.