Skip to content

Commit a678fe4

Browse files
authored
Merge pull request #3804 from element-hq/feature/fga/update_create_room_flow
Knocking : update create room flow
2 parents 47d7eac + 2ab6289 commit a678fe4

File tree

57 files changed

+743
-418
lines changed

Some content is hidden

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

57 files changed

+743
-418
lines changed

features/createroom/impl/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies {
4040
implementation(projects.libraries.usersearch.impl)
4141
implementation(projects.services.analytics.api)
4242
implementation(libs.coil.compose)
43+
implementation(projects.libraries.featureflag.api)
4344
api(projects.features.createroom.api)
4445

4546
testImplementation(libs.test.junit)
@@ -56,6 +57,7 @@ dependencies {
5657
testImplementation(projects.libraries.permissions.test)
5758
testImplementation(projects.libraries.usersearch.test)
5859
testImplementation(projects.features.createroom.test)
60+
testImplementation(projects.libraries.featureflag.test)
5961
testImplementation(projects.tests.testutils)
6062
testImplementation(libs.androidx.compose.ui.test.junit)
6163
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
package io.element.android.features.createroom.impl
99

1010
import android.net.Uri
11-
import io.element.android.features.createroom.impl.configureroom.RoomPrivacy
11+
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
1212
import io.element.android.libraries.matrix.api.user.MatrixUser
1313
import kotlinx.collections.immutable.ImmutableList
1414
import kotlinx.collections.immutable.persistentListOf
@@ -18,5 +18,7 @@ data class CreateRoomConfig(
1818
val topic: String? = null,
1919
val avatarUri: Uri? = null,
2020
val invites: ImmutableList<MatrixUser> = persistentListOf(),
21-
val privacy: RoomPrivacy = RoomPrivacy.Private,
22-
)
21+
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
22+
) {
23+
val isValid = roomName.isNullOrEmpty().not() && roomVisibility.isValid()
24+
}

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

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
package io.element.android.features.createroom.impl
99

1010
import android.net.Uri
11-
import io.element.android.features.createroom.impl.configureroom.RoomPrivacy
11+
import io.element.android.features.createroom.impl.configureroom.RoomAccess
12+
import io.element.android.features.createroom.impl.configureroom.RoomAccessItem
13+
import io.element.android.features.createroom.impl.configureroom.RoomAddress
14+
import io.element.android.features.createroom.impl.configureroom.RoomAddressErrorState
15+
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem
16+
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
1217
import io.element.android.features.createroom.impl.di.CreateRoomScope
1318
import io.element.android.features.createroom.impl.userlist.UserListDataStore
1419
import io.element.android.libraries.androidutils.file.safeDelete
@@ -17,6 +22,7 @@ import kotlinx.collections.immutable.toImmutableList
1722
import kotlinx.coroutines.flow.Flow
1823
import kotlinx.coroutines.flow.MutableStateFlow
1924
import kotlinx.coroutines.flow.combine
25+
import kotlinx.coroutines.flow.getAndUpdate
2026
import java.io.File
2127
import javax.inject.Inject
2228

@@ -31,28 +37,89 @@ class CreateRoomDataStore @Inject constructor(
3137
field = value
3238
}
3339

34-
fun getCreateRoomConfig(): Flow<CreateRoomConfig> = combine(
40+
val createRoomConfigWithInvites: Flow<CreateRoomConfig> = combine(
3541
selectedUserListDataStore.selectedUsers(),
3642
createRoomConfigFlow,
3743
) { selectedUsers, config ->
3844
config.copy(invites = selectedUsers.toImmutableList())
3945
}
4046

41-
fun setRoomName(roomName: String?) {
42-
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(roomName = roomName?.takeIf { it.isNotEmpty() }))
47+
fun setRoomName(roomName: String) {
48+
createRoomConfigFlow.getAndUpdate { config ->
49+
/*
50+
val newVisibility = when (config.roomVisibility) {
51+
is RoomVisibilityState.Public -> {
52+
val roomAddress = config.roomVisibility.roomAddress
53+
if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) {
54+
config.roomVisibility.copy(
55+
roomAddress = RoomAddress.AutoFilled(roomName),
56+
)
57+
} else {
58+
config.roomVisibility
59+
}
60+
}
61+
else -> config.roomVisibility
62+
}
63+
*/
64+
config.copy(
65+
roomName = roomName.takeIf { it.isNotEmpty() },
66+
)
67+
}
4368
}
4469

45-
fun setTopic(topic: String?) {
46-
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(topic = topic?.takeIf { it.isNotEmpty() }))
70+
fun setTopic(topic: String) {
71+
createRoomConfigFlow.getAndUpdate { config ->
72+
config.copy(topic = topic.takeIf { it.isNotEmpty() })
73+
}
4774
}
4875

4976
fun setAvatarUri(uri: Uri?, cached: Boolean = false) {
5077
cachedAvatarUri = uri.takeIf { cached }
51-
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(avatarUri = uri))
78+
createRoomConfigFlow.getAndUpdate { config ->
79+
config.copy(avatarUri = uri)
80+
}
81+
}
82+
83+
fun setRoomVisibility(visibility: RoomVisibilityItem) {
84+
createRoomConfigFlow.getAndUpdate { config ->
85+
config.copy(
86+
roomVisibility = when (visibility) {
87+
RoomVisibilityItem.Private -> RoomVisibilityState.Private
88+
RoomVisibilityItem.Public -> RoomVisibilityState.Public(
89+
roomAddress = RoomAddress.AutoFilled(config.roomName.orEmpty()),
90+
roomAddressErrorState = RoomAddressErrorState.None,
91+
roomAccess = RoomAccess.Anyone,
92+
)
93+
}
94+
)
95+
}
5296
}
5397

54-
fun setPrivacy(privacy: RoomPrivacy) {
55-
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(privacy = privacy))
98+
fun setRoomAddress(address: String) {
99+
createRoomConfigFlow.getAndUpdate { config ->
100+
config.copy(
101+
roomVisibility = when (config.roomVisibility) {
102+
is RoomVisibilityState.Public -> config.roomVisibility.copy(roomAddress = RoomAddress.Edited(address))
103+
else -> config.roomVisibility
104+
}
105+
)
106+
}
107+
}
108+
109+
fun setRoomAccess(access: RoomAccessItem) {
110+
createRoomConfigFlow.getAndUpdate { config ->
111+
config.copy(
112+
roomVisibility = when (config.roomVisibility) {
113+
is RoomVisibilityState.Public -> {
114+
when (access) {
115+
RoomAccessItem.Anyone -> config.roomVisibility.copy(roomAccess = RoomAccess.Anyone)
116+
RoomAccessItem.AskToJoin -> config.roomVisibility.copy(roomAccess = RoomAccess.Knocking)
117+
}
118+
}
119+
else -> config.roomVisibility
120+
}
121+
)
122+
}
56123
}
57124

58125
fun clearCachedData() {

features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt

Lines changed: 0 additions & 101 deletions
This file was deleted.

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

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

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

10-
import io.element.android.features.createroom.impl.CreateRoomConfig
1110
import io.element.android.libraries.matrix.api.user.MatrixUser
1211
import io.element.android.libraries.matrix.ui.media.AvatarAction
1312

1413
sealed interface ConfigureRoomEvents {
1514
data class RoomNameChanged(val name: String) : ConfigureRoomEvents
1615
data class TopicChanged(val topic: String) : ConfigureRoomEvents
17-
data class RoomPrivacyChanged(val privacy: RoomPrivacy) : ConfigureRoomEvents
18-
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
19-
data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents
16+
data class RoomVisibilityChanged(val visibilityItem: RoomVisibilityItem) : ConfigureRoomEvents
17+
data class RoomAccessChanged(val roomAccess: RoomAccessItem) : ConfigureRoomEvents
18+
data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents
19+
data class RemoveUserFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
20+
data object CreateRoom : ConfigureRoomEvents
2021
data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents
2122
data object CancelCreateRoom : ConfigureRoomEvents
2223
}

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

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import io.element.android.libraries.architecture.AsyncAction
2424
import io.element.android.libraries.architecture.Presenter
2525
import io.element.android.libraries.architecture.runCatchingUpdatingState
2626
import io.element.android.libraries.core.mimetype.MimeTypes
27+
import io.element.android.libraries.featureflag.api.FeatureFlagService
28+
import io.element.android.libraries.featureflag.api.FeatureFlags
2729
import io.element.android.libraries.matrix.api.MatrixClient
2830
import io.element.android.libraries.matrix.api.core.RoomId
2931
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
@@ -38,6 +40,7 @@ import io.element.android.services.analytics.api.AnalyticsService
3840
import kotlinx.collections.immutable.toImmutableList
3941
import kotlinx.coroutines.CoroutineScope
4042
import kotlinx.coroutines.launch
43+
import timber.log.Timber
4144
import javax.inject.Inject
4245

4346
class ConfigureRoomPresenter @Inject constructor(
@@ -47,14 +50,17 @@ class ConfigureRoomPresenter @Inject constructor(
4750
private val mediaPreProcessor: MediaPreProcessor,
4851
private val analyticsService: AnalyticsService,
4952
permissionsPresenterFactory: PermissionsPresenter.Factory,
53+
private val featureFlagService: FeatureFlagService,
5054
) : Presenter<ConfigureRoomState> {
5155
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
5256
private var pendingPermissionRequest = false
5357

5458
@Composable
5559
override fun present(): ConfigureRoomState {
5660
val cameraPermissionState = cameraPermissionPresenter.present()
57-
val createRoomConfig = dataStore.getCreateRoomConfig().collectAsState(CreateRoomConfig())
61+
val createRoomConfig = dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig())
62+
val homeserverName = remember { matrixClient.userIdServerName() }
63+
val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false)
5864

5965
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
6066
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) },
@@ -92,9 +98,11 @@ class ConfigureRoomPresenter @Inject constructor(
9298
when (event) {
9399
is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name)
94100
is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic)
95-
is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy)
96-
is ConfigureRoomEvents.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
97-
is ConfigureRoomEvents.CreateRoom -> createRoom(event.config)
101+
is ConfigureRoomEvents.RoomVisibilityChanged -> dataStore.setRoomVisibility(event.visibilityItem)
102+
is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
103+
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
104+
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
105+
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig.value)
98106
is ConfigureRoomEvents.HandleAvatarAction -> {
99107
when (event.action) {
100108
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
@@ -113,10 +121,12 @@ class ConfigureRoomPresenter @Inject constructor(
113121
}
114122

115123
return ConfigureRoomState(
124+
isKnockFeatureEnabled = isKnockFeatureEnabled,
116125
config = createRoomConfig.value,
117126
avatarActions = avatarActions,
118127
createRoomAction = createRoomAction.value,
119128
cameraPermissionState = cameraPermissionState,
129+
homeserverName = homeserverName,
120130
eventSink = ::handleEvents,
121131
)
122132
}
@@ -127,21 +137,40 @@ class ConfigureRoomPresenter @Inject constructor(
127137
) = launch {
128138
suspend {
129139
val avatarUrl = config.avatarUri?.let { uploadAvatar(it) }
130-
val params = CreateRoomParameters(
131-
name = config.roomName,
132-
topic = config.topic,
133-
isEncrypted = config.privacy == RoomPrivacy.Private,
134-
isDirect = false,
135-
visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE,
136-
preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT,
137-
invite = config.invites.map { it.userId },
138-
avatar = avatarUrl,
139-
)
140-
matrixClient.createRoom(params).getOrThrow()
141-
.also {
140+
val params = if (config.roomVisibility is RoomVisibilityState.Public) {
141+
CreateRoomParameters(
142+
name = config.roomName,
143+
topic = config.topic,
144+
isEncrypted = false,
145+
isDirect = false,
146+
visibility = RoomVisibility.PUBLIC,
147+
joinRuleOverride = config.roomVisibility.roomAccess.toJoinRule(),
148+
preset = RoomPreset.PUBLIC_CHAT,
149+
invite = config.invites.map { it.userId },
150+
avatar = avatarUrl,
151+
canonicalAlias = config.roomVisibility.roomAddress()
152+
)
153+
} else {
154+
CreateRoomParameters(
155+
name = config.roomName,
156+
topic = config.topic,
157+
isEncrypted = config.roomVisibility is RoomVisibilityState.Private,
158+
isDirect = false,
159+
visibility = RoomVisibility.PRIVATE,
160+
preset = RoomPreset.PRIVATE_CHAT,
161+
invite = config.invites.map { it.userId },
162+
avatar = avatarUrl,
163+
)
164+
}
165+
matrixClient.createRoom(params)
166+
.onFailure { failure ->
167+
Timber.e(failure, "Failed to create room")
168+
}
169+
.onSuccess {
142170
dataStore.clearCachedData()
143171
analyticsService.capture(CreatedRoom(isDM = false))
144172
}
173+
.getOrThrow()
145174
}.runCatchingUpdatingState(createRoomAction)
146175
}
147176

0 commit comments

Comments
 (0)