create room : start adding new options in ui
This commit is contained in:
@@ -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<MatrixUser> = persistentListOf(),
|
||||
val privacy: RoomPrivacy = RoomPrivacy.Private,
|
||||
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
|
||||
)
|
||||
|
||||
@@ -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<CreateRoomConfig> = combine(
|
||||
val createRoomConfig: Flow<CreateRoomConfig> = 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() {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -24,7 +24,10 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||
roomName = "Room 101",
|
||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
invites = aMatrixUserList().toImmutableList(),
|
||||
privacy = RoomPrivacy.Public,
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room 101"),
|
||||
roomAccess = RoomAccess.Knocking
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -11,16 +11,22 @@ import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
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.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -29,22 +35,26 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
||||
import io.element.android.features.createroom.impl.components.RoomAccessOption
|
||||
import io.element.android.features.createroom.impl.components.RoomVisibilityOption
|
||||
import io.element.android.libraries.designsystem.components.LabelledTextField
|
||||
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.modifiers.clearFocusOnTap
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
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.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
|
||||
@@ -103,23 +113,38 @@ fun ConfigureRoomView(
|
||||
)
|
||||
if (state.config.invites.isNotEmpty()) {
|
||||
SelectedUsersRowList(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp),
|
||||
selectedUsers = state.config.invites,
|
||||
onUserRemove = {
|
||||
focusManager.clearFocus()
|
||||
state.eventSink(ConfigureRoomEvents.RemoveFromSelection(it))
|
||||
state.eventSink(ConfigureRoomEvents.RemoveUserFromSelection(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
RoomPrivacyOptions(
|
||||
modifier = Modifier.padding(bottom = 40.dp),
|
||||
selected = state.config.privacy,
|
||||
RoomVisibilityOptions(
|
||||
selected = when (state.config.roomVisibility) {
|
||||
is RoomVisibilityState.Private -> 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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<RoomPrivacyItem> {
|
||||
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()
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user