From f6938fa737a6465f124586d2e96c0fc87fc9a192 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 25 Oct 2024 18:27:23 +0200 Subject: [PATCH] create room : start adding new options in ui --- .../createroom/impl/CreateRoomConfig.kt | 4 +- .../createroom/impl/CreateRoomDataStore.kt | 83 +++++++++- .../impl/components/RoomAccessOption.kt | 87 ++++++++++ ...ivacyOption.kt => RoomVisibilityOption.kt} | 51 +++--- .../impl/configureroom/ConfigureRoomEvents.kt | 6 +- .../configureroom/ConfigureRoomPresenter.kt | 15 +- .../ConfigureRoomStateProvider.kt | 5 +- .../impl/configureroom/ConfigureRoomView.kt | 156 ++++++++++++++++-- .../{RoomPrivacy.kt => RoomAccess.kt} | 7 +- .../impl/configureroom/RoomAccessItem.kt | 26 +++ .../impl/configureroom/RoomAddress.kt | 13 ++ .../impl/configureroom/RoomPrivacyItem.kt | 45 ----- .../impl/configureroom/RoomVisibilityItem.kt | 30 ++++ .../impl/configureroom/RoomVisibilityState.kt | 21 +++ .../ConfigureRoomPresenterTest.kt | 8 +- 15 files changed, 447 insertions(+), 110 deletions(-) create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomAccessOption.kt rename features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/{RoomPrivacyOption.kt => RoomVisibilityOption.kt} (68%) rename features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/{RoomPrivacy.kt => RoomAccess.kt} (67%) create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt delete mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomPrivacyItem.kt create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt index 731dc27d07..a83673886d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomConfig.kt @@ -8,7 +8,7 @@ package io.element.android.features.createroom.impl import android.net.Uri -import io.element.android.features.createroom.impl.configureroom.RoomPrivacy +import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -18,5 +18,5 @@ data class CreateRoomConfig( val topic: String? = null, val avatarUri: Uri? = null, val invites: ImmutableList = persistentListOf(), - val privacy: RoomPrivacy = RoomPrivacy.Private, + val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt index 5925ca7818..17d7d5cc3d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt @@ -8,7 +8,11 @@ package io.element.android.features.createroom.impl import android.net.Uri -import io.element.android.features.createroom.impl.configureroom.RoomPrivacy +import io.element.android.features.createroom.impl.configureroom.RoomAccess +import io.element.android.features.createroom.impl.configureroom.RoomAccessItem +import io.element.android.features.createroom.impl.configureroom.RoomAddress +import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem +import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState import io.element.android.features.createroom.impl.di.CreateRoomScope import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.libraries.androidutils.file.safeDelete @@ -17,7 +21,9 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.getAndUpdate import java.io.File +import java.text.Normalizer import javax.inject.Inject @SingleIn(CreateRoomScope::class) @@ -31,28 +37,87 @@ class CreateRoomDataStore @Inject constructor( field = value } - fun getCreateRoomConfig(): Flow = combine( + val createRoomConfig: Flow = combine( selectedUserListDataStore.selectedUsers(), createRoomConfigFlow, ) { selectedUsers, config -> config.copy(invites = selectedUsers.toImmutableList()) } - fun setRoomName(roomName: String?) { - createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(roomName = roomName?.takeIf { it.isNotEmpty() })) + fun setRoomName(roomName: String) { + createRoomConfigFlow.getAndUpdate { config -> + val newVisibility = when (config.roomVisibility) { + is RoomVisibilityState.Public -> { + val roomAddress = config.roomVisibility.roomAddress + if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) { + config.roomVisibility.copy( + roomAddress = RoomAddress.AutoFilled(roomName), + ) + } else { + config.roomVisibility + } + } + else -> config.roomVisibility + } + config.copy( + roomName = roomName.takeIf { it.isNotEmpty() }, + roomVisibility = newVisibility, + ) + } } - fun setTopic(topic: String?) { - createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(topic = topic?.takeIf { it.isNotEmpty() })) + fun setTopic(topic: String) { + createRoomConfigFlow.getAndUpdate { config -> + config.copy(topic = topic.takeIf { it.isNotEmpty() }) + } } fun setAvatarUri(uri: Uri?, cached: Boolean = false) { cachedAvatarUri = uri.takeIf { cached } - createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(avatarUri = uri)) + createRoomConfigFlow.getAndUpdate { config -> + config.copy(avatarUri = uri) + } } - fun setPrivacy(privacy: RoomPrivacy) { - createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(privacy = privacy)) + fun setRoomVisibility(visibility: RoomVisibilityItem) { + createRoomConfigFlow.getAndUpdate { config -> + config.copy( + roomVisibility = when (visibility) { + RoomVisibilityItem.Private -> RoomVisibilityState.Private + RoomVisibilityItem.Public -> RoomVisibilityState.Public( + roomAddress = RoomAddress.AutoFilled(config.roomName.orEmpty()), + roomAccess = RoomAccess.Anyone, + ) + } + ) + } + } + + fun setRoomAddress(address: String) { + createRoomConfigFlow.getAndUpdate { config -> + config.copy( + roomVisibility = when (config.roomVisibility) { + is RoomVisibilityState.Public -> config.roomVisibility.copy(roomAddress = RoomAddress.Edited(address)) + else -> config.roomVisibility + } + ) + } + } + + fun setRoomAccess(access: RoomAccessItem) { + createRoomConfigFlow.getAndUpdate { config -> + config.copy( + roomVisibility = when (config.roomVisibility) { + is RoomVisibilityState.Public -> { + when (access) { + RoomAccessItem.Anyone -> config.roomVisibility.copy(roomAccess = RoomAccess.Anyone) + RoomAccessItem.AskToJoin -> config.roomVisibility.copy(roomAccess = RoomAccess.Knocking) + } + } + else -> config.roomVisibility + } + ) + } } fun clearCachedData() { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomAccessOption.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomAccessOption.kt new file mode 100644 index 0000000000..4d39f698be --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomAccessOption.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.selection.selectable +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.createroom.impl.configureroom.RoomAccessItem +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.RadioButton +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun RoomAccessOption( + roomAccessItem: RoomAccessItem, + onOptionClick: (RoomAccessItem) -> Unit, + modifier: Modifier = Modifier, + isSelected: Boolean = false, +) { + Row( + modifier + .fillMaxWidth() + .selectable( + selected = isSelected, + onClick = { onOptionClick(roomAccessItem) }, + role = Role.RadioButton, + ) + ) { + Column(Modifier.weight(1f)) { + Text( + text = stringResource(roomAccessItem.title), + style = ElementTheme.typography.fontBodyLgRegular, + color = MaterialTheme.colorScheme.primary, + ) + Spacer(Modifier.size(8.dp)) + Text( + text = stringResource(roomAccessItem.description), + style = ElementTheme.typography.fontBodySmRegular, + color = MaterialTheme.colorScheme.tertiary, + ) + } + RadioButton( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(48.dp), + selected = isSelected, + // null recommended for accessibility with screenreaders + onClick = null + ) + } +} + +@PreviewsDayNight +@Composable +internal fun RoomAccessOptionPreview() = ElementPreview { + val aRoomAccessItem = RoomAccessItem.Anyone + Column { + RoomAccessOption( + roomAccessItem = aRoomAccessItem, + onOptionClick = {}, + isSelected = true, + ) + RoomAccessOption( + roomAccessItem = aRoomAccessItem, + onOptionClick = {}, + isSelected = false, + ) + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomVisibilityOption.kt similarity index 68% rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt rename to features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomVisibilityOption.kt index 302f6134e6..cf6ab077a4 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomVisibilityOption.kt @@ -7,6 +7,8 @@ package io.element.android.features.createroom.impl.components +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -14,15 +16,17 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.features.createroom.impl.configureroom.RoomPrivacyItem -import io.element.android.features.createroom.impl.configureroom.roomPrivacyItems +import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon @@ -30,9 +34,9 @@ import io.element.android.libraries.designsystem.theme.components.RadioButton import io.element.android.libraries.designsystem.theme.components.Text @Composable -fun RoomPrivacyOption( - roomPrivacyItem: RoomPrivacyItem, - onOptionClick: (RoomPrivacyItem) -> Unit, +fun RoomVisibilityOption( + roomPrivacyItem: RoomVisibilityItem, + onOptionClick: (RoomVisibilityItem) -> Unit, modifier: Modifier = Modifier, isSelected: Boolean = false, ) { @@ -44,28 +48,31 @@ fun RoomPrivacyOption( onClick = { onOptionClick(roomPrivacyItem) }, role = Role.RadioButton, ) - .padding(8.dp), ) { - Icon( - modifier = Modifier.padding(horizontal = 8.dp), - resourceId = roomPrivacyItem.icon, - contentDescription = null, - tint = MaterialTheme.colorScheme.secondary, - ) - - Column( - Modifier - .weight(1f) - .padding(horizontal = 8.dp) + Box( + modifier = modifier + .size(30.dp) + .clip(RoundedCornerShape(8.dp)) + .background(ElementTheme.colors.bgSubtleSecondary) + .padding(3.dp), + contentAlignment = Alignment.Center, ) { + Icon( + resourceId = roomPrivacyItem.icon, + contentDescription = null, + tint = if(isSelected) ElementTheme.colors.iconPrimary else ElementTheme.colors.iconSecondary, + ) + } + Spacer(Modifier.size(16.dp)) + Column(Modifier.weight(1f)) { Text( - text = roomPrivacyItem.title, + text = stringResource(roomPrivacyItem.title), style = ElementTheme.typography.fontBodyLgRegular, color = MaterialTheme.colorScheme.primary, ) Spacer(Modifier.size(3.dp)) Text( - text = roomPrivacyItem.description, + text = stringResource(roomPrivacyItem.description), style = ElementTheme.typography.fontBodySmRegular, color = MaterialTheme.colorScheme.tertiary, ) @@ -85,14 +92,14 @@ fun RoomPrivacyOption( @PreviewsDayNight @Composable internal fun RoomPrivacyOptionPreview() = ElementPreview { - val aRoomPrivacyItem = roomPrivacyItems().first() + val aRoomPrivacyItem = RoomVisibilityItem.Private Column { - RoomPrivacyOption( + RoomVisibilityOption( roomPrivacyItem = aRoomPrivacyItem, onOptionClick = {}, isSelected = true, ) - RoomPrivacyOption( + RoomVisibilityOption( roomPrivacyItem = aRoomPrivacyItem, onOptionClick = {}, isSelected = false, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt index d6f647fe61..048cd1cfe0 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt @@ -14,8 +14,10 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction sealed interface ConfigureRoomEvents { data class RoomNameChanged(val name: String) : ConfigureRoomEvents data class TopicChanged(val topic: String) : ConfigureRoomEvents - data class RoomPrivacyChanged(val privacy: RoomPrivacy) : ConfigureRoomEvents - data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents + data class RoomVisibilityChanged(val visibilityItem: RoomVisibilityItem) : ConfigureRoomEvents + data class RoomAccessChanged(val roomAccess: RoomAccessItem) : ConfigureRoomEvents + data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents + data class RemoveUserFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents data object CancelCreateRoom : ConfigureRoomEvents diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 09553d6160..a07bb57c84 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -54,7 +54,7 @@ class ConfigureRoomPresenter @Inject constructor( @Composable override fun present(): ConfigureRoomState { val cameraPermissionState = cameraPermissionPresenter.present() - val createRoomConfig = dataStore.getCreateRoomConfig().collectAsState(CreateRoomConfig()) + val createRoomConfig = dataStore.createRoomConfig.collectAsState(CreateRoomConfig()) val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker( onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) }, @@ -92,8 +92,10 @@ class ConfigureRoomPresenter @Inject constructor( when (event) { is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name) is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic) - is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy) - is ConfigureRoomEvents.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser) + is ConfigureRoomEvents.RoomVisibilityChanged -> dataStore.setRoomVisibility(event.visibilityItem) + is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser) + is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess) + is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress) is ConfigureRoomEvents.CreateRoom -> createRoom(event.config) is ConfigureRoomEvents.HandleAvatarAction -> { when (event.action) { @@ -109,6 +111,7 @@ class ConfigureRoomPresenter @Inject constructor( } ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = AsyncAction.Uninitialized + } } @@ -130,10 +133,10 @@ class ConfigureRoomPresenter @Inject constructor( val params = CreateRoomParameters( name = config.roomName, topic = config.topic, - isEncrypted = config.privacy == RoomPrivacy.Private, + isEncrypted = config.roomVisibility is RoomVisibilityState.Private, isDirect = false, - visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE, - preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT, + visibility = if (config.roomVisibility is RoomVisibilityState.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE, + preset = if (config.roomVisibility is RoomVisibilityState.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT, invite = config.invites.map { it.userId }, avatar = avatarUrl, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt index ba49ffcbdd..e89ec0e927 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt @@ -24,7 +24,10 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider RoomVisibilityItem.Private + is RoomVisibilityState.Public -> RoomVisibilityItem.Public + }, onOptionClick = { focusManager.clearFocus() - state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy)) + state.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(it)) }, ) + if (state.config.roomVisibility is RoomVisibilityState.Public) { + RoomAccessOptions( + selected = state.config.roomVisibility.roomAccess, + onOptionClick = { + focusManager.clearFocus() + state.eventSink(ConfigureRoomEvents.RoomAccessChanged(it)) + }, + ) + RoomAddress( + modifier = Modifier.padding(horizontal = 16.dp), + address = state.config.roomVisibility.roomAddress, + onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) }, + ) + } } } @@ -221,24 +246,125 @@ private fun RoomTopic( } @Composable -private fun RoomPrivacyOptions( - selected: RoomPrivacy?, - onOptionClick: (RoomPrivacyItem) -> Unit, +private fun ConfigureRoomOptions( + title: String, + verticalArrangement: Arrangement.Vertical, + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Column( + modifier = modifier + .selectableGroup() + .padding(horizontal = 12.dp), + verticalArrangement = verticalArrangement, + ) { + Text( + text = title, + style = ElementTheme.typography.fontBodyLgMedium, + color = ElementTheme.colors.textPrimary, + ) + content() + } +} + +@Composable +private fun RoomVisibilityOptions( + selected: RoomVisibilityItem, + onOptionClick: (RoomVisibilityItem) -> Unit, modifier: Modifier = Modifier, ) { - val items = roomPrivacyItems() - Column(modifier = modifier.selectableGroup()) { - items.forEach { item -> - RoomPrivacyOption( + ConfigureRoomOptions( + title = "Room visibility", + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + RoomVisibilityItem.entries.forEach { item -> + RoomVisibilityOption( roomPrivacyItem = item, - isSelected = selected == item.privacy, + isSelected = item == selected, onOptionClick = onOptionClick, ) } } } +@Composable +private fun RoomAccessOptions( + selected: RoomAccess, + onOptionClick: (RoomAccessItem) -> Unit, + modifier: Modifier = Modifier, +) { + ConfigureRoomOptions( + title = "Room access", + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + RoomAccessItem.entries.forEach { item -> + RoomAccessOption( + roomAccessItem = item, + isSelected = when (item) { + RoomAccessItem.Anyone -> selected == RoomAccess.Anyone + RoomAccessItem.AskToJoin -> selected == RoomAccess.Knocking + }, + onOptionClick = onOptionClick, + ) + } + } +} + +@Composable +private fun RoomAddress( + address: RoomAddress, + onAddressChange: (String) -> Unit, + modifier: Modifier = Modifier, +){ + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + style = ElementTheme.typography.fontBodyMdRegular, + color = MaterialTheme.colorScheme.primary, + text = "Room address", + ) + + TextField( + modifier = Modifier.fillMaxWidth(), + value = when(address) { + is RoomAddress.AutoFilled -> address.address + is RoomAddress.Edited -> address.address + }, + leadingIcon = { + Text( + text = "#", + style = ElementTheme.typography.fontBodyLgMedium, + color = ElementTheme.colors.textSecondary, + ) + }, + trailingIcon = { + Text( + text = ":myserver.com", + style = ElementTheme.typography.fontBodyLgMedium, + color = ElementTheme.colors.textSecondary, + modifier = Modifier.padding(end = 16.dp) + ) + }, + supportingText = { + Text( + text = "In order for this room to be visible in the public room directory, you will need to a room address. ", + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + }, + onValueChange = onAddressChange, + singleLine = true, + ) + } +} + @PreviewsDayNight +@PreviewWithLargeHeight @Composable internal fun ConfigureRoomViewPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) = ElementPreview { ConfigureRoomView( diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomPrivacy.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt similarity index 67% rename from features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomPrivacy.kt rename to features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt index d376b84681..3a86280f11 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomPrivacy.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright 2024 New Vector Ltd. * * SPDX-License-Identifier: AGPL-3.0-only * Please see LICENSE in the repository root for full details. @@ -7,7 +7,6 @@ package io.element.android.features.createroom.impl.configureroom -enum class RoomPrivacy { - Private, - Public, +enum class RoomAccess { + Anyone, Knocking, Invite } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt new file mode 100644 index 0000000000..6c21935196 --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.configureroom + +import androidx.annotation.StringRes +import io.element.android.libraries.ui.strings.CommonStrings + +enum class RoomAccessItem( + @StringRes val title: Int, + @StringRes val description: Int +) { + Anyone( + title = CommonStrings.screen_create_room_access_section_anyone_option_title, + description = CommonStrings.screen_create_room_access_section_anyone_option_description, + ), + AskToJoin( + title = CommonStrings.screen_create_room_access_section_knocking_option_title, + description = CommonStrings.screen_create_room_access_section_knocking_option_description, + ) +} + diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt new file mode 100644 index 0000000000..a26888e08d --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.configureroom + +sealed interface RoomAddress { + data class AutoFilled(val address: String) : RoomAddress + data class Edited(val address: String) : RoomAddress +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomPrivacyItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomPrivacyItem.kt deleted file mode 100644 index a0b7e4cc05..0000000000 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomPrivacyItem.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.createroom.impl.configureroom - -import androidx.annotation.DrawableRes -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import io.element.android.features.createroom.impl.R -import io.element.android.libraries.designsystem.icons.CompoundDrawables -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList - -data class RoomPrivacyItem( - val privacy: RoomPrivacy, - @DrawableRes val icon: Int, - val title: String, - val description: String, -) - -@Composable -fun roomPrivacyItems(): ImmutableList { - return RoomPrivacy.entries - .map { - when (it) { - RoomPrivacy.Private -> RoomPrivacyItem( - privacy = it, - icon = CompoundDrawables.ic_compound_lock_solid, - title = stringResource(R.string.screen_create_room_private_option_title), - description = stringResource(R.string.screen_create_room_private_option_description), - ) - RoomPrivacy.Public -> RoomPrivacyItem( - privacy = it, - icon = CompoundDrawables.ic_compound_public, - title = stringResource(R.string.screen_create_room_public_option_title), - description = stringResource(R.string.screen_create_room_public_option_description), - ) - } - } - .toImmutableList() -} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt new file mode 100644 index 0000000000..12909cdd5e --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.configureroom + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import io.element.android.features.createroom.impl.R +import io.element.android.libraries.designsystem.icons.CompoundDrawables + +enum class RoomVisibilityItem( + @DrawableRes val icon: Int, + @StringRes val title: Int, + @StringRes val description: Int +) { + Private( + icon = CompoundDrawables.ic_compound_lock, + title = R.string.screen_create_room_private_option_title, + description = R.string.screen_create_room_private_option_description, + ), + Public( + icon = CompoundDrawables.ic_compound_public, + title = R.string.screen_create_room_public_option_title, + description = R.string.screen_create_room_public_option_description, + ) +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt new file mode 100644 index 0000000000..0a50a98a3f --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.configureroom + +sealed interface RoomVisibilityState { + val roomAccess: RoomAccess + + data object Private : RoomVisibilityState { + override val roomAccess: RoomAccess = RoomAccess.Invite + } + + data class Public( + val roomAddress: RoomAddress, + override val roomAccess: RoomAccess + ) : RoomVisibilityState +} diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt index 9294f95b78..3c09e34ce0 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTest.kt @@ -103,7 +103,7 @@ class ConfigureRoomPresenterTest { assertThat(initialState.config.topic).isNull() assertThat(initialState.config.invites).isEmpty() assertThat(initialState.config.avatarUri).isNull() - assertThat(initialState.config.privacy).isEqualTo(RoomPrivacy.Private) + assertThat(initialState.config.roomVisibility).isEqualTo(RoomVisibilityState.Private) } } @@ -200,13 +200,13 @@ class ConfigureRoomPresenterTest { assertThat(newState.config).isEqualTo(expectedConfig) // Room privacy - newState.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(RoomPrivacy.Public)) + newState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityState.Public)) newState = awaitItem() - expectedConfig = expectedConfig.copy(privacy = RoomPrivacy.Public) + expectedConfig = expectedConfig.copy(roomVisibility = RoomVisibilityState.Public) assertThat(newState.config).isEqualTo(expectedConfig) // Remove user - newState.eventSink(ConfigureRoomEvents.RemoveFromSelection(selectedUser1)) + newState.eventSink(ConfigureRoomEvents.RemoveUserFromSelection(selectedUser1)) newState = awaitItem() expectedConfig = expectedConfig.copy(invites = expectedConfig.invites.minus(selectedUser1).toImmutableList()) assertThat(newState.config).isEqualTo(expectedConfig)