Skip to content

Commit 346e364

Browse files
authored
Merge pull request #4212 from element-hq/feature/fga/room_settings_security_privacy
Feature : room settings - security and privacy
2 parents 6dca2bf + c10da01 commit 346e364

File tree

125 files changed

+3387
-347
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

125 files changed

+3387
-347
lines changed

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import androidx.compose.runtime.getValue
1717
import androidx.compose.runtime.mutableStateOf
1818
import androidx.compose.runtime.remember
1919
import androidx.compose.runtime.rememberCoroutineScope
20-
import androidx.compose.runtime.rememberUpdatedState
2120
import im.vector.app.features.analytics.plan.CreatedRoom
2221
import io.element.android.features.createroom.impl.CreateRoomConfig
2322
import io.element.android.features.createroom.impl.CreateRoomDataStore
@@ -31,23 +30,22 @@ import io.element.android.libraries.matrix.api.MatrixClient
3130
import io.element.android.libraries.matrix.api.core.RoomId
3231
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
3332
import io.element.android.libraries.matrix.api.createroom.RoomPreset
34-
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
3533
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
36-
import io.element.android.libraries.matrix.api.roomAliasFromName
34+
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
3735
import io.element.android.libraries.matrix.ui.media.AvatarAction
36+
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
37+
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEffect
3838
import io.element.android.libraries.mediapickers.api.PickerProvider
3939
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
4040
import io.element.android.libraries.permissions.api.PermissionsEvents
4141
import io.element.android.libraries.permissions.api.PermissionsPresenter
4242
import io.element.android.services.analytics.api.AnalyticsService
4343
import kotlinx.collections.immutable.toImmutableList
4444
import kotlinx.coroutines.CoroutineScope
45-
import kotlinx.coroutines.delay
4645
import kotlinx.coroutines.launch
4746
import timber.log.Timber
48-
import java.util.Optional
4947
import javax.inject.Inject
50-
import kotlin.jvm.optionals.getOrNull
48+
import kotlin.jvm.optionals.getOrDefault
5149

5250
class ConfigureRoomPresenter @Inject constructor(
5351
private val dataStore: CreateRoomDataStore,
@@ -96,7 +94,12 @@ class ConfigureRoomPresenter @Inject constructor(
9694
}
9795
}
9896

99-
RoomAddressValidityEffect(createRoomConfig.roomVisibility.roomAddress()) { newRoomAddressValidity ->
97+
RoomAddressValidityEffect(
98+
client = matrixClient,
99+
roomAliasHelper = roomAliasHelper,
100+
newRoomAddress = createRoomConfig.roomVisibility.roomAddress().getOrDefault(""),
101+
knownRoomAddress = null,
102+
) { newRoomAddressValidity ->
100103
roomAddressValidity.value = newRoomAddressValidity
101104
}
102105

@@ -146,39 +149,6 @@ class ConfigureRoomPresenter @Inject constructor(
146149
)
147150
}
148151

149-
@Composable
150-
private fun RoomAddressValidityEffect(
151-
roomAddress: Optional<String>,
152-
onRoomAddressValidityChange: (RoomAddressValidity) -> Unit,
153-
) {
154-
val onChange by rememberUpdatedState(onRoomAddressValidityChange)
155-
LaunchedEffect(roomAddress) {
156-
val roomAliasName = roomAddress.getOrNull().orEmpty()
157-
if (roomAliasName.isEmpty()) {
158-
onChange(RoomAddressValidity.Unknown)
159-
return@LaunchedEffect
160-
}
161-
// debounce the room address validation
162-
delay(300)
163-
val roomAlias = matrixClient.roomAliasFromName(roomAliasName).getOrNull()
164-
if (roomAlias == null || !roomAliasHelper.isRoomAliasValid(roomAlias)) {
165-
onChange(RoomAddressValidity.InvalidSymbols)
166-
} else {
167-
matrixClient.resolveRoomAlias(roomAlias)
168-
.onSuccess { resolved ->
169-
if (resolved.isPresent) {
170-
onChange(RoomAddressValidity.NotAvailable)
171-
} else {
172-
onChange(RoomAddressValidity.Valid)
173-
}
174-
}
175-
.onFailure {
176-
onChange(RoomAddressValidity.Valid)
177-
}
178-
}
179-
}
180-
}
181-
182152
private fun CoroutineScope.createRoom(
183153
config: CreateRoomConfig,
184154
createRoomAction: MutableState<AsyncAction<RoomId>>
@@ -191,7 +161,7 @@ class ConfigureRoomPresenter @Inject constructor(
191161
topic = config.topic,
192162
isEncrypted = false,
193163
isDirect = false,
194-
visibility = RoomVisibility.PUBLIC,
164+
visibility = RoomVisibility.Public,
195165
joinRuleOverride = config.roomVisibility.roomAccess.toJoinRule(),
196166
preset = RoomPreset.PUBLIC_CHAT,
197167
invite = config.invites.map { it.userId },
@@ -204,7 +174,7 @@ class ConfigureRoomPresenter @Inject constructor(
204174
topic = config.topic,
205175
isEncrypted = config.roomVisibility is RoomVisibilityState.Private,
206176
isDirect = false,
207-
visibility = RoomVisibility.PRIVATE,
177+
visibility = RoomVisibility.Private,
208178
preset = RoomPreset.PRIVATE_CHAT,
209179
invite = config.invites.map { it.userId },
210180
avatar = avatarUrl,

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import io.element.android.features.createroom.impl.CreateRoomConfig
1111
import io.element.android.libraries.architecture.AsyncAction
1212
import io.element.android.libraries.matrix.api.core.RoomId
1313
import io.element.android.libraries.matrix.ui.media.AvatarAction
14+
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
1415
import io.element.android.libraries.permissions.api.PermissionsState
1516
import kotlinx.collections.immutable.ImmutableList
1617

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import io.element.android.libraries.architecture.AsyncAction
1313
import io.element.android.libraries.matrix.api.core.RoomId
1414
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
1515
import io.element.android.libraries.matrix.ui.media.AvatarAction
16+
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
1617
import io.element.android.libraries.permissions.api.PermissionsState
1718
import io.element.android.libraries.permissions.api.aPermissionsState
1819
import kotlinx.collections.immutable.toImmutableList

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt

Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.PaddingValues
1616
import androidx.compose.foundation.layout.Row
1717
import androidx.compose.foundation.layout.Spacer
1818
import androidx.compose.foundation.layout.consumeWindowInsets
19-
import androidx.compose.foundation.layout.fillMaxWidth
2019
import androidx.compose.foundation.layout.imePadding
2120
import androidx.compose.foundation.layout.padding
2221
import androidx.compose.foundation.rememberScrollState
@@ -58,6 +57,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
5857
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
5958
import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList
6059
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
60+
import io.element.android.libraries.matrix.ui.room.address.RoomAddressField
6161
import io.element.android.libraries.permissions.api.PermissionsView
6262
import io.element.android.libraries.ui.strings.CommonStrings
6363

@@ -142,10 +142,12 @@ fun ConfigureRoomView(
142142
)
143143
RoomAddressField(
144144
modifier = Modifier.padding(horizontal = 16.dp),
145-
address = state.config.roomVisibility.roomAddress,
145+
address = state.config.roomVisibility.roomAddress.value,
146146
homeserverName = state.homeserverName,
147147
addressValidity = state.roomAddressValidity,
148148
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
149+
label = stringResource(R.string.screen_create_room_room_address_section_title),
150+
supportingText = stringResource(R.string.screen_create_room_room_address_section_footer),
149151
)
150152
Spacer(Modifier)
151153
}
@@ -318,47 +320,6 @@ private fun RoomAccessOptions(
318320
}
319321
}
320322

321-
@Composable
322-
private fun RoomAddressField(
323-
address: RoomAddress,
324-
homeserverName: String,
325-
addressValidity: RoomAddressValidity,
326-
onAddressChange: (String) -> Unit,
327-
modifier: Modifier = Modifier,
328-
) {
329-
TextField(
330-
modifier = modifier.fillMaxWidth(),
331-
value = address.value,
332-
label = stringResource(R.string.screen_create_room_room_address_section_title),
333-
leadingIcon = {
334-
Text(
335-
text = "#",
336-
style = ElementTheme.typography.fontBodyLgMedium,
337-
color = ElementTheme.colors.textSecondary,
338-
)
339-
},
340-
trailingIcon = {
341-
Text(
342-
text = homeserverName,
343-
style = ElementTheme.typography.fontBodyLgMedium,
344-
color = ElementTheme.colors.textSecondary,
345-
)
346-
},
347-
supportingText = when (addressValidity) {
348-
RoomAddressValidity.InvalidSymbols -> {
349-
stringResource(CommonStrings.error_room_address_invalid_symbols)
350-
}
351-
RoomAddressValidity.NotAvailable -> {
352-
stringResource(CommonStrings.error_room_address_already_exists)
353-
}
354-
else -> stringResource(R.string.screen_create_room_room_address_section_footer)
355-
},
356-
isError = addressValidity.isError(),
357-
onValueChange = onAddressChange,
358-
singleLine = true,
359-
)
360-
}
361-
362323
@PreviewWithLargeHeight
363324
@Composable
364325
internal fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77

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

10-
import io.element.android.libraries.matrix.api.createroom.JoinRuleOverride
10+
import io.element.android.libraries.matrix.api.room.join.JoinRule
1111

1212
enum class RoomAccess {
1313
Anyone,
1414
Knocking
1515
}
1616

17-
fun RoomAccess.toJoinRule(): JoinRuleOverride {
17+
fun RoomAccess.toJoinRule(): JoinRule? {
1818
return when (this) {
19-
RoomAccess.Anyone -> JoinRuleOverride.None
20-
RoomAccess.Knocking -> JoinRuleOverride.Knock
19+
RoomAccess.Anyone -> null
20+
RoomAccess.Knocking -> JoinRule.Knock
2121
}
2222
}

features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
3030
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
3131
import io.element.android.libraries.matrix.ui.components.aMatrixUser
3232
import io.element.android.libraries.matrix.ui.media.AvatarAction
33+
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
3334
import io.element.android.libraries.mediapickers.api.PickerProvider
3435
import io.element.android.libraries.mediapickers.test.FakePickerProvider
3536
import io.element.android.libraries.mediaupload.api.MediaPreProcessor

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ class IdentityChangeStatePresenterTest {
6767

6868
@Test
6969
fun `present - when the clear room emits identity change, the presenter does not emit new state`() = runTest {
70-
val room = FakeMatrixRoom(isEncrypted = false)
70+
val room = FakeMatrixRoom(
71+
isEncrypted = false,
72+
enableEncryptionResult = { Result.success(Unit) }
73+
)
7174
val presenter = createIdentityChangeStatePresenter(room)
7275
presenter.test {
7376
val initialState = awaitItem()

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import io.element.android.features.roomdetails.impl.members.RoomMemberListNode
3333
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode
3434
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode
3535
import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode
36+
import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode
3637
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
3738
import io.element.android.libraries.architecture.BackstackWithOverlayBox
3839
import io.element.android.libraries.architecture.BaseFlowNode
@@ -114,6 +115,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
114115

115116
@Parcelize
116117
data object KnockRequestsList : NavTarget
118+
119+
@Parcelize
120+
data object SecurityAndPrivacy : NavTarget
117121
}
118122

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

167+
override fun openSecurityAndPrivacy() {
168+
backstack.push(NavTarget.SecurityAndPrivacy)
169+
}
170+
163171
override fun onJoinCall() {
164172
val inputs = CallType.RoomCall(
165173
sessionId = room.sessionId,
@@ -290,6 +298,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
290298
NavTarget.KnockRequestsList -> {
291299
knockRequestsListEntryPoint.createNode(this, buildContext)
292300
}
301+
NavTarget.SecurityAndPrivacy -> {
302+
createNode<SecurityAndPrivacyFlowNode>(buildContext)
303+
}
293304
}
294305
}
295306

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class RoomDetailsNode @AssistedInject constructor(
4949
fun openAdminSettings()
5050
fun openPinnedMessagesList()
5151
fun openKnockRequestsList()
52+
fun openSecurityAndPrivacy()
5253
fun onJoinCall()
5354
}
5455

@@ -121,6 +122,10 @@ class RoomDetailsNode @AssistedInject constructor(
121122
callbacks.forEach { it.openKnockRequestsList() }
122123
}
123124

125+
private fun openSecurityAndPrivacy() {
126+
callbacks.forEach { it.openSecurityAndPrivacy() }
127+
}
128+
124129
@Composable
125130
override fun View(modifier: Modifier) {
126131
val context = LocalContext.current
@@ -153,6 +158,7 @@ class RoomDetailsNode @AssistedInject constructor(
153158
onJoinCallClick = ::onJoinCall,
154159
onPinnedMessagesClick = ::openPinnedMessages,
155160
onKnockRequestsClick = ::openKnockRequestsLists,
161+
onSecurityAndPrivacyClick = ::openSecurityAndPrivacy
156162
)
157163
}
158164
}

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package io.element.android.features.roomdetails.impl
99

1010
import androidx.compose.runtime.Composable
1111
import androidx.compose.runtime.LaunchedEffect
12-
import androidx.compose.runtime.State
1312
import androidx.compose.runtime.collectAsState
1413
import androidx.compose.runtime.derivedStateOf
1514
import androidx.compose.runtime.getValue
@@ -24,6 +23,7 @@ import io.element.android.features.leaveroom.api.LeaveRoomState
2423
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
2524
import io.element.android.features.roomcall.api.RoomCallState
2625
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
26+
import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState
2727
import io.element.android.libraries.architecture.Presenter
2828
import io.element.android.libraries.core.bool.orFalse
2929
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@@ -104,7 +104,7 @@ class RoomDetailsPresenter @Inject constructor(
104104
val dmMember by room.getDirectRoomMember(membersState)
105105
val currentMember by room.getCurrentRoomMember(membersState)
106106
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
107-
val roomType by getRoomType(dmMember, currentMember)
107+
val roomType = getRoomType(dmMember, currentMember)
108108
val roomCallState = roomCallStatePresenter.present()
109109

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

148148
val roomMemberDetailsState = roomMemberDetailsPresenter?.present()
149149

150+
val securityAndPrivacyPermissions = room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value)
151+
val canShowSecurityAndPrivacy by remember {
152+
derivedStateOf {
153+
isKnockRequestsEnabled && roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny
154+
}
155+
}
156+
150157
return RoomDetailsState(
151158
roomId = room.roomId,
152159
roomName = roomName,
153-
roomAlias = room.alias,
160+
roomAlias = room.canonicalAlias,
154161
roomAvatarUrl = roomAvatar,
155162
roomTopic = topicState,
156163
memberCount = room.joinedMemberCount,
@@ -172,6 +179,7 @@ class RoomDetailsPresenter @Inject constructor(
172179
pinnedMessagesCount = pinnedMessagesCount,
173180
canShowKnockRequests = canShowKnockRequests,
174181
knockRequestsCount = knockRequestsCount,
182+
canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
175183
eventSink = ::handleEvents,
176184
)
177185
}
@@ -187,16 +195,14 @@ class RoomDetailsPresenter @Inject constructor(
187195
private fun getRoomType(
188196
dmMember: RoomMember?,
189197
currentMember: RoomMember?,
190-
): State<RoomDetailsType> = remember(dmMember, currentMember) {
191-
derivedStateOf {
192-
if (dmMember != null && currentMember != null) {
193-
RoomDetailsType.Dm(
194-
me = currentMember,
195-
otherMember = dmMember,
196-
)
197-
} else {
198-
RoomDetailsType.Room
199-
}
198+
): RoomDetailsType = remember(dmMember, currentMember) {
199+
if (dmMember != null && currentMember != null) {
200+
RoomDetailsType.Dm(
201+
me = currentMember,
202+
otherMember = dmMember,
203+
)
204+
} else {
205+
RoomDetailsType.Room
200206
}
201207
}
202208

0 commit comments

Comments
 (0)