Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Knocking : update create room flow #3804

Merged
merged 16 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -31,28 +37,87 @@ class CreateRoomDataStore @Inject constructor(
field = value
}

fun getCreateRoomConfig(): Flow<CreateRoomConfig> = combine(
val createRoomConfig: Flow<CreateRoomConfig> = combine(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to createRoomConfigWithInvites?

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() })
jmartinesp marked this conversation as resolved.
Show resolved Hide resolved
}
}

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 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 setPrivacy(privacy: RoomPrivacy) {
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(privacy = privacy))
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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably use a ListItem too.

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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,36 @@

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
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
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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, maybe this should use a ListItem inside instead?

roomPrivacyItem: RoomVisibilityItem,
onOptionClick: (RoomVisibilityItem) -> Unit,
modifier: Modifier = Modifier,
isSelected: Boolean = false,
) {
Expand All @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have RoundedIconAtom for this.

) {
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,
)
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down Expand Up @@ -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) {
Expand All @@ -109,6 +111,7 @@ class ConfigureRoomPresenter @Inject constructor(
}

ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = AsyncAction.Uninitialized

}
}

Expand All @@ -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,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
),
),
),
)
Expand Down
Loading