Merge pull request #308 from vector-im/feature/fre/create_room_screen
Create a room screen (UI)
This commit is contained in:
commit
7d5b53b534
50 changed files with 1140 additions and 28 deletions
1
changelog.d/110.feature
Normal file
1
changelog.d/110.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[Create and join rooms] Create a room screen (UI)
|
||||||
|
|
@ -49,12 +49,14 @@ dependencies {
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.features.userlist.api)
|
implementation(projects.features.userlist.api)
|
||||||
api(projects.features.createroom.api)
|
api(projects.features.createroom.api)
|
||||||
|
implementation(libs.coil.compose) // FIXME temp
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.molecule.runtime)
|
testImplementation(libs.molecule.runtime)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.features.userlist.impl)
|
testImplementation(projects.features.userlist.impl)
|
||||||
testImplementation(projects.features.userlist.test)
|
testImplementation(projects.features.userlist.test)
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,14 @@ import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
||||||
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
|
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode
|
||||||
import io.element.android.features.createroom.impl.root.CreateRoomRootNode
|
import io.element.android.features.createroom.impl.root.CreateRoomRootNode
|
||||||
import io.element.android.libraries.architecture.BackstackNode
|
import io.element.android.libraries.architecture.BackstackNode
|
||||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
|
|
@ -58,6 +60,9 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object NewRoom : NavTarget
|
object NewRoom : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class ConfigureRoom(val users: List<MatrixUser>) : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
|
|
@ -72,9 +77,19 @@ class CreateRoomFlowNode @AssistedInject constructor(
|
||||||
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomId) }
|
plugins<CreateRoomEntryPoint.Callback>().forEach { it.onOpenRoom(roomId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createNode<CreateRoomRootNode>(buildContext, plugins = listOf(callback))
|
createNode<CreateRoomRootNode>(context = buildContext, plugins = listOf(callback))
|
||||||
|
}
|
||||||
|
NavTarget.NewRoom -> {
|
||||||
|
val callback = object : AddPeopleNode.Callback {
|
||||||
|
override fun onContinue(selectedUsers: List<MatrixUser>) {
|
||||||
|
backstack.push(NavTarget.ConfigureRoom(selectedUsers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createNode<AddPeopleNode>(context = buildContext, plugins = listOf(callback))
|
||||||
|
}
|
||||||
|
is NavTarget.ConfigureRoom -> {
|
||||||
|
createNode<ConfigureRoomNode>(context = buildContext, plugins = listOf(ConfigureRoomNode.Inputs(navTarget.users)))
|
||||||
}
|
}
|
||||||
NavTarget.NewRoom -> createNode<AddPeopleNode>(buildContext)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,12 @@ import androidx.compose.ui.Modifier
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.bumble.appyx.core.node.Node
|
import com.bumble.appyx.core.node.Node
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import com.bumble.appyx.core.plugin.plugins
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
|
||||||
@ContributesNode(SessionScope::class)
|
@ContributesNode(SessionScope::class)
|
||||||
class AddPeopleNode @AssistedInject constructor(
|
class AddPeopleNode @AssistedInject constructor(
|
||||||
|
|
@ -33,6 +35,14 @@ class AddPeopleNode @AssistedInject constructor(
|
||||||
private val presenter: AddPeoplePresenter,
|
private val presenter: AddPeoplePresenter,
|
||||||
) : Node(buildContext, plugins = plugins) {
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onContinue(selectedUsers: List<MatrixUser>)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onContinue(selectedUsers: List<MatrixUser>) {
|
||||||
|
plugins<Callback>().forEach { it.onContinue(selectedUsers) }
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun View(modifier: Modifier) {
|
override fun View(modifier: Modifier) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
|
|
@ -40,7 +50,7 @@ class AddPeopleNode @AssistedInject constructor(
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onBackPressed = { navigateUp() },
|
onBackPressed = { navigateUp() },
|
||||||
onNextPressed = { },
|
onNextPressed = this::onContinue,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.features.userlist.api.UserListView
|
|
||||||
import io.element.android.features.createroom.impl.R
|
import io.element.android.features.createroom.impl.R
|
||||||
|
import io.element.android.features.userlist.api.UserListView
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
|
@ -38,6 +38,7 @@ import io.element.android.libraries.designsystem.theme.components.CenterAlignedT
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
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.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
import io.element.android.libraries.ui.strings.R as StringR
|
import io.element.android.libraries.ui.strings.R as StringR
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -46,7 +47,7 @@ fun AddPeopleView(
|
||||||
state: AddPeopleState,
|
state: AddPeopleState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onBackPressed: () -> Unit = {},
|
onBackPressed: () -> Unit = {},
|
||||||
onNextPressed: () -> Unit = {},
|
onNextPressed: (List<MatrixUser>) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val eventSink = state.eventSink
|
val eventSink = state.eventSink
|
||||||
|
|
||||||
|
|
@ -56,7 +57,7 @@ fun AddPeopleView(
|
||||||
AddPeopleViewTopBar(
|
AddPeopleViewTopBar(
|
||||||
hasSelectedUsers = state.userListState.selectedUsers.isNotEmpty(),
|
hasSelectedUsers = state.userListState.selectedUsers.isNotEmpty(),
|
||||||
onBackPressed = onBackPressed,
|
onBackPressed = onBackPressed,
|
||||||
onNextPressed = onNextPressed,
|
onNextPressed = { onNextPressed(state.userListState.selectedUsers) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.components
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.AddAPhoto
|
||||||
|
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.graphics.painter.ColorPainter
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
|
||||||
|
import io.element.android.libraries.designsystem.theme.LocalColors
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Avatar(
|
||||||
|
avatarUri: Uri?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
) {
|
||||||
|
val commonModifier = modifier
|
||||||
|
.size(70.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
|
||||||
|
if (avatarUri != null) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val model = ImageRequest.Builder(context)
|
||||||
|
.data(avatarUri)
|
||||||
|
.build()
|
||||||
|
AsyncImage(
|
||||||
|
modifier = commonModifier,
|
||||||
|
model = model,
|
||||||
|
placeholder = debugPlaceholderBackground(ColorPainter(MaterialTheme.colorScheme.surfaceVariant)),
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(modifier = commonModifier.background(LocalColors.current.quinary)) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.AddAPhoto,
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(40.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AvatarLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AvatarDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview() {
|
||||||
|
Row {
|
||||||
|
Avatar(null)
|
||||||
|
Avatar(Uri.EMPTY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.features.createroom.impl.R
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LabelledTextField(
|
||||||
|
label: String,
|
||||||
|
value: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
placeholder: String = "",
|
||||||
|
maxLines: Int = 1,
|
||||||
|
onValueChange: (String) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
text = label
|
||||||
|
)
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = value,
|
||||||
|
placeholder = { Text(placeholder) },
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
maxLines = maxLines,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun LabelledTextFieldLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun LabelledTextFieldDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview() {
|
||||||
|
Column {
|
||||||
|
LabelledTextField(
|
||||||
|
label = stringResource(R.string.screen_create_room_room_name_label),
|
||||||
|
value = "",
|
||||||
|
placeholder = stringResource(R.string.screen_create_room_room_name_placeholder),
|
||||||
|
)
|
||||||
|
LabelledTextField(
|
||||||
|
label = stringResource(R.string.screen_create_room_room_name_label),
|
||||||
|
value = "a room name",
|
||||||
|
placeholder = stringResource(R.string.screen_create_room_room_name_placeholder),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.semantics.Role
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.RoomPrivacyItem
|
||||||
|
import io.element.android.features.createroom.impl.configureroom.roomPrivacyItems
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
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,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
onOptionSelected: (RoomPrivacyItem) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.selectable(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = { onOptionSelected(roomPrivacyItem) },
|
||||||
|
role = Role.RadioButton,
|
||||||
|
)
|
||||||
|
.padding(8.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
imageVector = roomPrivacyItem.icon,
|
||||||
|
contentDescription = "",
|
||||||
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = roomPrivacyItem.title,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(3.dp))
|
||||||
|
Text(
|
||||||
|
text = roomPrivacyItem.description,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 17.sp,
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.size(48.dp),
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = null // null recommended for accessibility with screenreaders
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun RoomPrivacyOptionLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun RoomPrivacyOptionDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview() {
|
||||||
|
val aRoomPrivacyItem = roomPrivacyItems().first()
|
||||||
|
Column {
|
||||||
|
RoomPrivacyOption(
|
||||||
|
roomPrivacyItem = aRoomPrivacyItem,
|
||||||
|
isSelected = true,
|
||||||
|
)
|
||||||
|
RoomPrivacyOption(
|
||||||
|
roomPrivacyItem = aRoomPrivacyItem,
|
||||||
|
isSelected = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
|
sealed interface ConfigureRoomEvents {
|
||||||
|
data class RoomNameChanged(val name: String) : ConfigureRoomEvents
|
||||||
|
data class TopicChanged(val topic: String) : ConfigureRoomEvents
|
||||||
|
data class AvatarUriChanged(val uri: Uri?) : ConfigureRoomEvents
|
||||||
|
data class RoomPrivacyChanged(val privacy: RoomPrivacy?) : ConfigureRoomEvents
|
||||||
|
object CreateRoom : ConfigureRoomEvents
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
|
||||||
|
@ContributesNode(SessionScope::class)
|
||||||
|
class ConfigureRoomNode @AssistedInject constructor(
|
||||||
|
@Assisted buildContext: BuildContext,
|
||||||
|
@Assisted plugins: List<Plugin>,
|
||||||
|
private val presenterFactory: ConfigureRoomPresenter.Factory,
|
||||||
|
) : Node(buildContext, plugins = plugins) {
|
||||||
|
|
||||||
|
data class Inputs(
|
||||||
|
val selectedUsers: List<MatrixUser>
|
||||||
|
) : NodeInputs
|
||||||
|
|
||||||
|
private val inputs: Inputs = inputs()
|
||||||
|
private val presenter by lazy {
|
||||||
|
presenterFactory.create(ConfigureRoomPresenterArgs(inputs.selectedUsers))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun View(modifier: Modifier) {
|
||||||
|
val state = presenter.present()
|
||||||
|
ConfigureRoomView(
|
||||||
|
state = state,
|
||||||
|
modifier = modifier,
|
||||||
|
onBackPressed = { navigateUp() } // TODO we should keep in memory the current view state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
class ConfigureRoomPresenter @AssistedInject constructor(
|
||||||
|
@Assisted val args: ConfigureRoomPresenterArgs,
|
||||||
|
) : Presenter<ConfigureRoomState> {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(args: ConfigureRoomPresenterArgs): ConfigureRoomPresenter
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun present(): ConfigureRoomState {
|
||||||
|
var roomName by rememberSaveable { mutableStateOf("") }
|
||||||
|
var topic by rememberSaveable { mutableStateOf("") }
|
||||||
|
var avatarUri by rememberSaveable { mutableStateOf<Uri?>(null) }
|
||||||
|
var privacy by rememberSaveable { mutableStateOf<RoomPrivacy?>(null) }
|
||||||
|
val isCreateButtonEnabled by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
roomName.isNotEmpty() && privacy != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleEvents(event: ConfigureRoomEvents) {
|
||||||
|
when (event) {
|
||||||
|
is ConfigureRoomEvents.AvatarUriChanged -> avatarUri = event.uri
|
||||||
|
is ConfigureRoomEvents.RoomNameChanged -> roomName = event.name
|
||||||
|
is ConfigureRoomEvents.TopicChanged -> topic = event.topic
|
||||||
|
is ConfigureRoomEvents.RoomPrivacyChanged -> privacy = event.privacy
|
||||||
|
ConfigureRoomEvents.CreateRoom -> Unit // TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfigureRoomState(
|
||||||
|
selectedUsers = args.selectedUsers.toImmutableList(),
|
||||||
|
roomName = roomName,
|
||||||
|
topic = topic,
|
||||||
|
avatarUri = avatarUri,
|
||||||
|
privacy = privacy,
|
||||||
|
isCreateButtonEnabled = isCreateButtonEnabled,
|
||||||
|
eventSink = ::handleEvents,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
|
||||||
|
data class ConfigureRoomPresenterArgs(
|
||||||
|
val selectedUsers: List<MatrixUser>,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import io.element.android.libraries.matrix.ui.model.MatrixUser
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
data class ConfigureRoomState(
|
||||||
|
val selectedUsers: ImmutableList<MatrixUser>,
|
||||||
|
val roomName: String,
|
||||||
|
val topic: String,
|
||||||
|
val avatarUri: Uri?,
|
||||||
|
val privacy: RoomPrivacy?,
|
||||||
|
val isCreateButtonEnabled: Boolean,
|
||||||
|
val eventSink: (ConfigureRoomEvents) -> Unit
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomState> {
|
||||||
|
override val values: Sequence<ConfigureRoomState>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aConfigureRoomState(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aConfigureRoomState() = ConfigureRoomState(
|
||||||
|
selectedUsers = aMatrixUserList().toImmutableList(),
|
||||||
|
roomName = "",
|
||||||
|
topic = "",
|
||||||
|
avatarUri = null,
|
||||||
|
privacy = null,
|
||||||
|
isCreateButtonEnabled = false,
|
||||||
|
eventSink = {}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
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.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import io.element.android.features.createroom.impl.R
|
||||||
|
import io.element.android.features.createroom.impl.components.Avatar
|
||||||
|
import io.element.android.features.createroom.impl.components.LabelledTextField
|
||||||
|
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
||||||
|
import io.element.android.features.userlist.api.SelectedUsersList
|
||||||
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar
|
||||||
|
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.ui.strings.R as StringR
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ConfigureRoomView(
|
||||||
|
state: ConfigureRoomState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onBackPressed: () -> Unit = {},
|
||||||
|
) {
|
||||||
|
val selectedUsersListState = rememberLazyListState()
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
topBar = {
|
||||||
|
ConfigureRoomToolbar(
|
||||||
|
isNextActionEnabled = state.isCreateButtonEnabled,
|
||||||
|
onBackPressed = onBackPressed,
|
||||||
|
onNextPressed = { state.eventSink(ConfigureRoomEvents.CreateRoom) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(padding),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
|
) {
|
||||||
|
RoomNameWithAvatar(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
avatarUri = state.avatarUri,
|
||||||
|
roomName = state.roomName,
|
||||||
|
onRoomNameChanged = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
|
||||||
|
)
|
||||||
|
RoomTopic(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
topic = state.topic,
|
||||||
|
onTopicChanged = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
|
||||||
|
)
|
||||||
|
SelectedUsersList(
|
||||||
|
listState = selectedUsersListState,
|
||||||
|
contentPadding = PaddingValues(horizontal = 24.dp),
|
||||||
|
selectedUsers = state.selectedUsers,
|
||||||
|
onUserRemoved = { }, // TODO
|
||||||
|
)
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
RoomPrivacyOptions(
|
||||||
|
modifier = Modifier.padding(bottom = 40.dp),
|
||||||
|
selected = state.privacy,
|
||||||
|
onOptionSelected = { state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ConfigureRoomToolbar(
|
||||||
|
isNextActionEnabled: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onBackPressed: () -> Unit = {},
|
||||||
|
onNextPressed: () -> Unit = {},
|
||||||
|
) {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
modifier = modifier,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.screen_create_room_title),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = { BackButton(onClick = onBackPressed) },
|
||||||
|
actions = {
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp),
|
||||||
|
enabled = isNextActionEnabled,
|
||||||
|
onClick = onNextPressed,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(StringR.string.action_create),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RoomNameWithAvatar(
|
||||||
|
avatarUri: Uri?,
|
||||||
|
roomName: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onAvatarClick: () -> Unit = {},
|
||||||
|
onRoomNameChanged: (String) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Avatar(
|
||||||
|
avatarUri = avatarUri,
|
||||||
|
onClick = onAvatarClick,
|
||||||
|
)
|
||||||
|
|
||||||
|
LabelledTextField(
|
||||||
|
label = stringResource(R.string.screen_create_room_room_name_label),
|
||||||
|
value = roomName,
|
||||||
|
placeholder = stringResource(R.string.screen_create_room_room_name_placeholder),
|
||||||
|
onValueChange = onRoomNameChanged
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RoomTopic(
|
||||||
|
topic: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onTopicChanged: (String) -> Unit = {},
|
||||||
|
) {
|
||||||
|
LabelledTextField(
|
||||||
|
modifier = modifier,
|
||||||
|
label = stringResource(R.string.screen_create_room_topic_label),
|
||||||
|
value = topic,
|
||||||
|
placeholder = stringResource(R.string.screen_create_room_topic_placeholder),
|
||||||
|
onValueChange = onTopicChanged,
|
||||||
|
maxLines = 3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RoomPrivacyOptions(
|
||||||
|
selected: RoomPrivacy?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onOptionSelected: (RoomPrivacyItem) -> Unit = {},
|
||||||
|
) {
|
||||||
|
val items = roomPrivacyItems()
|
||||||
|
Column(modifier = modifier.selectableGroup()) {
|
||||||
|
items.forEach { item ->
|
||||||
|
RoomPrivacyOption(
|
||||||
|
roomPrivacyItem = item,
|
||||||
|
isSelected = selected == item.privacy,
|
||||||
|
onOptionSelected = onOptionSelected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
|
||||||
|
ElementPreviewLight { ContentToPreview(state) }
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ConfigureRoomViewDarkPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
|
||||||
|
ElementPreviewDark { ContentToPreview(state) }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview(state: ConfigureRoomState) {
|
||||||
|
ConfigureRoomView(
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
enum class RoomPrivacy {
|
||||||
|
Public,
|
||||||
|
Private,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Lock
|
||||||
|
import androidx.compose.material.icons.outlined.Public
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import io.element.android.features.createroom.impl.R
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
data class RoomPrivacyItem(
|
||||||
|
val privacy: RoomPrivacy,
|
||||||
|
val icon: ImageVector,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun roomPrivacyItems(): ImmutableList<RoomPrivacyItem> {
|
||||||
|
return RoomPrivacy.values()
|
||||||
|
.map {
|
||||||
|
when (it) {
|
||||||
|
RoomPrivacy.Public -> RoomPrivacyItem(
|
||||||
|
privacy = it,
|
||||||
|
icon = Icons.Outlined.Lock,
|
||||||
|
title = stringResource(R.string.screen_create_room_private_option_title),
|
||||||
|
description = stringResource(R.string.screen_create_room_private_option_description),
|
||||||
|
)
|
||||||
|
RoomPrivacy.Private -> RoomPrivacyItem(
|
||||||
|
privacy = it,
|
||||||
|
icon = Icons.Outlined.Public,
|
||||||
|
title = stringResource(R.string.screen_create_room_public_option_title),
|
||||||
|
description = stringResource(R.string.screen_create_room_public_option_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toImmutableList()
|
||||||
|
}
|
||||||
|
|
@ -110,7 +110,7 @@ fun CreateRoomRootView(
|
||||||
}
|
}
|
||||||
is Async.Failure -> {
|
is Async.Failure -> {
|
||||||
RetryDialog(
|
RetryDialog(
|
||||||
content = stringResource(id = StringR.string.screen_start_chat_error_starting_chat),
|
content = stringResource(id = R.string.screen_start_chat_error_starting_chat),
|
||||||
onDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
onDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
||||||
onRetry = {
|
onRetry = {
|
||||||
state.userListState.selectedUsers.firstOrNull()
|
state.userListState.selectedUsers.firstOrNull()
|
||||||
|
|
@ -156,7 +156,7 @@ fun CreateRoomActionButtonsList(
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
CreateRoomActionButton(
|
CreateRoomActionButton(
|
||||||
iconRes = DrawableR.drawable.ic_groups,
|
iconRes = DrawableR.drawable.ic_groups,
|
||||||
text = stringResource(id = R.string.screen_create_room_action_create_room),
|
text = stringResource(id = StringR.string.action_create_a_room),
|
||||||
onClick = onNewRoomClicked,
|
onClick = onNewRoomClicked,
|
||||||
)
|
)
|
||||||
CreateRoomActionButton(
|
CreateRoomActionButton(
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,6 @@
|
||||||
<string name="screen_create_room_action_create_room">"Nueva sala"</string>
|
<string name="screen_create_room_action_create_room">"Nueva sala"</string>
|
||||||
<string name="screen_create_room_action_invite_people">"Invitar gente"</string>
|
<string name="screen_create_room_action_invite_people">"Invitar gente"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Añadir personas"</string>
|
<string name="screen_create_room_add_people_title">"Añadir personas"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"Se ha producido un error al intentar iniciar un chat"</string>
|
||||||
|
<string name="screen_start_chat_unknown_profile">"No podemos validar el ID de Matrix de este usuario. Es posible que no reciba la invitación."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -3,4 +3,6 @@
|
||||||
<string name="screen_create_room_action_create_room">"Nuova stanza"</string>
|
<string name="screen_create_room_action_create_room">"Nuova stanza"</string>
|
||||||
<string name="screen_create_room_action_invite_people">"Invita persone"</string>
|
<string name="screen_create_room_action_invite_people">"Invita persone"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Aggiungi persone"</string>
|
<string name="screen_create_room_add_people_title">"Aggiungi persone"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"Si è verificato un errore durante il tentativo di avviare una chat"</string>
|
||||||
|
<string name="screen_start_chat_unknown_profile">"Non possiamo convalidare l\'ID Matrix di questo utente. L\'invito potrebbe non essere ricevuto."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -3,4 +3,6 @@
|
||||||
<string name="screen_create_room_action_create_room">"Cameră nouă"</string>
|
<string name="screen_create_room_action_create_room">"Cameră nouă"</string>
|
||||||
<string name="screen_create_room_action_invite_people">"Invitați persoane"</string>
|
<string name="screen_create_room_action_invite_people">"Invitați persoane"</string>
|
||||||
<string name="screen_create_room_add_people_title">"Adaugați persoane"</string>
|
<string name="screen_create_room_add_people_title">"Adaugați persoane"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"A apărut o eroare la încercarea începerii conversației"</string>
|
||||||
|
<string name="screen_start_chat_unknown_profile">"Nu am putut valida ID-ul Matrix al acestui utilizator. Este posibil ca invitația să nu fi fost primită."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -12,4 +12,6 @@
|
||||||
<string name="screen_create_room_title">"Create a room"</string>
|
<string name="screen_create_room_title">"Create a room"</string>
|
||||||
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
|
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
|
||||||
<string name="screen_create_room_topic_placeholder">"What is this room about?"</string>
|
<string name="screen_create_room_topic_placeholder">"What is this room about?"</string>
|
||||||
|
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
||||||
|
<string name="screen_start_chat_unknown_profile">"We can’t validate this user’s Matrix ID. The invite might not be received."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
|
||||||
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import app.cash.molecule.RecompositionClock
|
||||||
|
import app.cash.molecule.moleculeFlow
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
|
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_NAME
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
class ConfigureRoomPresenterTests {
|
||||||
|
|
||||||
|
private lateinit var presenter: ConfigureRoomPresenter
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
presenter = ConfigureRoomPresenter(ConfigureRoomPresenterArgs(emptyList()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - initial state`() = runTest {
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.roomName).isEmpty()
|
||||||
|
assertThat(initialState.topic).isEmpty()
|
||||||
|
assertThat(initialState.privacy).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - create room button is enabled only if the required fields are completed`() = runTest {
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.isCreateButtonEnabled).isFalse()
|
||||||
|
|
||||||
|
// Room name not empty
|
||||||
|
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
|
||||||
|
var newState: ConfigureRoomState = awaitItem()
|
||||||
|
assertThat(newState.roomName).isEqualTo(A_ROOM_NAME)
|
||||||
|
assertThat(newState.isCreateButtonEnabled).isFalse()
|
||||||
|
|
||||||
|
// Select privacy
|
||||||
|
initialState.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(RoomPrivacy.Private))
|
||||||
|
newState = awaitItem()
|
||||||
|
assertThat(newState.privacy).isEqualTo(RoomPrivacy.Private)
|
||||||
|
assertThat(newState.isCreateButtonEnabled).isTrue()
|
||||||
|
|
||||||
|
// Clear room name
|
||||||
|
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(""))
|
||||||
|
newState = awaitItem()
|
||||||
|
assertThat(newState.roomName).isEqualTo("")
|
||||||
|
assertThat(newState.isCreateButtonEnabled).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - state is updated when fields are changed`() = runTest {
|
||||||
|
moleculeFlow(RecompositionClock.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
// Room name
|
||||||
|
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
|
||||||
|
val stateAfterRoomNameChanged = awaitItem()
|
||||||
|
assertThat(stateAfterRoomNameChanged.roomName).isEqualTo(A_ROOM_NAME)
|
||||||
|
|
||||||
|
// Room topic
|
||||||
|
stateAfterRoomNameChanged.eventSink(ConfigureRoomEvents.TopicChanged(A_MESSAGE))
|
||||||
|
val stateAfterTopicChanged = awaitItem()
|
||||||
|
assertThat(stateAfterTopicChanged.topic).isEqualTo(A_MESSAGE)
|
||||||
|
|
||||||
|
// Room avatar
|
||||||
|
val anUri = Uri.parse(AN_AVATAR_URL)
|
||||||
|
stateAfterTopicChanged.eventSink(ConfigureRoomEvents.AvatarUriChanged(anUri))
|
||||||
|
val stateAfterAvatarUriChanged = awaitItem()
|
||||||
|
assertThat(stateAfterAvatarUriChanged.avatarUri).isEqualTo(anUri)
|
||||||
|
|
||||||
|
// Room privacy
|
||||||
|
stateAfterAvatarUriChanged.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(RoomPrivacy.Public))
|
||||||
|
val stateAfterPrivacyChanged = awaitItem()
|
||||||
|
assertThat(stateAfterPrivacyChanged.privacy).isEqualTo(RoomPrivacy.Public)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -21,6 +21,7 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
|
@ -97,7 +98,7 @@ fun UserListView(
|
||||||
if (state.isMultiSelectionEnabled && !state.isSearchActive && state.selectedUsers.isNotEmpty()) {
|
if (state.isMultiSelectionEnabled && !state.isSearchActive && state.selectedUsers.isNotEmpty()) {
|
||||||
SelectedUsersList(
|
SelectedUsersList(
|
||||||
listState = state.selectedUsersListState,
|
listState = state.selectedUsersListState,
|
||||||
modifier = Modifier.padding(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
selectedUsers = state.selectedUsers,
|
selectedUsers = state.selectedUsers,
|
||||||
onUserRemoved = {
|
onUserRemoved = {
|
||||||
state.eventSink(UserListEvents.RemoveFromSelection(it))
|
state.eventSink(UserListEvents.RemoveFromSelection(it))
|
||||||
|
|
@ -174,7 +175,7 @@ fun SearchUserBar(
|
||||||
if (isMultiSelectionEnabled && active && selectedUsers.isNotEmpty()) {
|
if (isMultiSelectionEnabled && active && selectedUsers.isNotEmpty()) {
|
||||||
SelectedUsersList(
|
SelectedUsersList(
|
||||||
listState = selectedUsersListState,
|
listState = selectedUsersListState,
|
||||||
modifier = Modifier.padding(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
selectedUsers = selectedUsers,
|
selectedUsers = selectedUsers,
|
||||||
onUserRemoved = onUserDeselected,
|
onUserRemoved = onUserDeselected,
|
||||||
)
|
)
|
||||||
|
|
@ -244,11 +245,13 @@ fun SelectedUsersList(
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
selectedUsers: ImmutableList<MatrixUser>,
|
selectedUsers: ImmutableList<MatrixUser>,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
contentPadding: PaddingValues = PaddingValues(0.dp),
|
||||||
onUserRemoved: (MatrixUser) -> Unit = {},
|
onUserRemoved: (MatrixUser) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
contentPadding = contentPadding,
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
) {
|
) {
|
||||||
items(selectedUsers.toList()) { matrixUser ->
|
items(selectedUsers.toList()) { matrixUser ->
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="error_no_compatible_app_found">"No se encontró ninguna aplicación compatible con esta acción."</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="error_no_compatible_app_found">"Non è stata trovata alcuna app compatibile per gestire questa azione."</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="error_no_compatible_app_found">"Nu a fost găsită nicio aplicație capabilă să gestioneze această acțiune."</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,20 @@
|
||||||
|
|
||||||
package io.element.android.libraries.designsystem.components.avatar
|
package io.element.android.libraries.designsystem.components.avatar
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
@Parcelize
|
||||||
data class AvatarData(
|
data class AvatarData(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val url: String? = null,
|
val url: String? = null,
|
||||||
|
@IgnoredOnParcel
|
||||||
val size: AvatarSize = AvatarSize.MEDIUM
|
val size: AvatarSize = AvatarSize.MEDIUM
|
||||||
) {
|
) : Parcelable {
|
||||||
fun getInitial(): String {
|
fun getInitial(): String {
|
||||||
val firstChar = name?.firstOrNull() ?: id.getOrNull(1) ?: '?'
|
val firstChar = name?.firstOrNull() ?: id.getOrNull(1) ?: '?'
|
||||||
return firstChar.uppercase()
|
return firstChar.uppercase()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.theme.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.material3.RadioButtonColors
|
||||||
|
import androidx.compose.material3.RadioButtonDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RadioButton(
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: (() -> Unit)?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
colors: RadioButtonColors = RadioButtonDefaults.colors(),
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.RadioButton(
|
||||||
|
selected = selected,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
colors = colors,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun RadioButtonLightPreview() = ElementPreviewLight { ContentToPreview() }
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
internal fun RadioButtonDarkPreview() = ElementPreviewDark { ContentToPreview() }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ContentToPreview() {
|
||||||
|
Column {
|
||||||
|
RadioButton(selected = false, onClick = {})
|
||||||
|
RadioButton(selected = true, onClick = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ plugins {
|
||||||
id("io.element.android-compose-library")
|
id("io.element.android-compose-library")
|
||||||
alias(libs.plugins.anvil)
|
alias(libs.plugins.anvil)
|
||||||
alias(libs.plugins.ksp)
|
alias(libs.plugins.ksp)
|
||||||
|
id("kotlin-parcelize")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,19 @@ fun aMatrixUser(id: String = "@id_of_alice:server.org", userName: String = "Alic
|
||||||
avatarData = anAvatarData()
|
avatarData = anAvatarData()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun aMatrixUserList() = listOf(
|
||||||
|
aMatrixUser("@alice:server.org", "Alice"),
|
||||||
|
aMatrixUser("@bob:server.org", "Bob"),
|
||||||
|
aMatrixUser("@carol:server.org", "Carol"),
|
||||||
|
aMatrixUser("@david:server.org", "David"),
|
||||||
|
aMatrixUser("@eve:server.org", "Eve"),
|
||||||
|
aMatrixUser("@justin:server.org", "Justin"),
|
||||||
|
aMatrixUser("@mallory:server.org", "Mallory"),
|
||||||
|
aMatrixUser("@susie:server.org", "Susie"),
|
||||||
|
aMatrixUser("@victor:server.org", "Victor"),
|
||||||
|
aMatrixUser("@walter:server.org", "Walter"),
|
||||||
|
)
|
||||||
|
|
||||||
open class MatrixUserWithNullProvider : PreviewParameterProvider<MatrixUser?> {
|
open class MatrixUserWithNullProvider : PreviewParameterProvider<MatrixUser?> {
|
||||||
override val values: Sequence<MatrixUser?>
|
override val values: Sequence<MatrixUser?>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,19 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.ui.model
|
package io.element.android.libraries.matrix.ui.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
@Immutable
|
@Immutable
|
||||||
data class MatrixUser(
|
data class MatrixUser(
|
||||||
val id: UserId,
|
val id: UserId,
|
||||||
val username: String? = null,
|
val username: String? = null,
|
||||||
val avatarData: AvatarData = AvatarData(id.value, username),
|
val avatarData: AvatarData = AvatarData(id.value, username),
|
||||||
)
|
) : Parcelable
|
||||||
|
|
||||||
fun MatrixUser.getBestName(): String {
|
fun MatrixUser.getBestName(): String {
|
||||||
return username?.takeIf { it.isNotEmpty() } ?: id.value
|
return username?.takeIf { it.isNotEmpty() } ?: id.value
|
||||||
|
|
|
||||||
|
|
@ -137,11 +137,9 @@
|
||||||
<string name="screen_room_member_details_unblock_alert_action">"Desbloquear"</string>
|
<string name="screen_room_member_details_unblock_alert_action">"Desbloquear"</string>
|
||||||
<string name="screen_room_member_details_unblock_alert_description">"Al desbloquear al usuario, podrás volver a ver todos sus mensajes."</string>
|
<string name="screen_room_member_details_unblock_alert_description">"Al desbloquear al usuario, podrás volver a ver todos sus mensajes."</string>
|
||||||
<string name="screen_room_member_details_unblock_user">"Desbloquear usuario"</string>
|
<string name="screen_room_member_details_unblock_user">"Desbloquear usuario"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Se ha producido un error al intentar iniciar un chat"</string>
|
|
||||||
<string name="screen_start_chat_unknown_profile">"No podemos validar el ID de Matrix de este usuario. Es posible que no reciba la invitación."</string>
|
|
||||||
<string name="settings_rageshake">"Agitar con fuerza"</string>
|
<string name="settings_rageshake">"Agitar con fuerza"</string>
|
||||||
<string name="settings_rageshake_detection_threshold">"Umbral de detección"</string>
|
<string name="settings_rageshake_detection_threshold">"Umbral de detección"</string>
|
||||||
<string name="settings_title_general">"General"</string>
|
<string name="settings_title_general">"General"</string>
|
||||||
<string name="settings_version_number">"Versión: %1$s (%2$s)"</string>
|
<string name="settings_version_number">"Versión: %1$s (%2$s)"</string>
|
||||||
<string name="test_language_identifier">"es"</string>
|
<string name="test_language_identifier">"es"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -137,11 +137,9 @@
|
||||||
<string name="screen_room_member_details_unblock_alert_action">"Sblocca"</string>
|
<string name="screen_room_member_details_unblock_alert_action">"Sblocca"</string>
|
||||||
<string name="screen_room_member_details_unblock_alert_description">"Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi."</string>
|
<string name="screen_room_member_details_unblock_alert_description">"Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi."</string>
|
||||||
<string name="screen_room_member_details_unblock_user">"Sblocca utente"</string>
|
<string name="screen_room_member_details_unblock_user">"Sblocca utente"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"Si è verificato un errore durante il tentativo di avviare una chat"</string>
|
|
||||||
<string name="screen_start_chat_unknown_profile">"Non possiamo convalidare l\'ID Matrix di questo utente. L\'invito potrebbe non essere ricevuto."</string>
|
|
||||||
<string name="settings_rageshake">"Rageshake"</string>
|
<string name="settings_rageshake">"Rageshake"</string>
|
||||||
<string name="settings_rageshake_detection_threshold">"Soglia di rilevamento"</string>
|
<string name="settings_rageshake_detection_threshold">"Soglia di rilevamento"</string>
|
||||||
<string name="settings_title_general">"Generali"</string>
|
<string name="settings_title_general">"Generali"</string>
|
||||||
<string name="settings_version_number">"Versione: %1$s (%2$s)"</string>
|
<string name="settings_version_number">"Versione: %1$s (%2$s)"</string>
|
||||||
<string name="test_language_identifier">"it"</string>
|
<string name="test_language_identifier">"it"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -139,11 +139,9 @@
|
||||||
<string name="screen_room_member_details_unblock_alert_action">"Deblocați"</string>
|
<string name="screen_room_member_details_unblock_alert_action">"Deblocați"</string>
|
||||||
<string name="screen_room_member_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
|
<string name="screen_room_member_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
|
||||||
<string name="screen_room_member_details_unblock_user">"Deblocați utilizatorul"</string>
|
<string name="screen_room_member_details_unblock_user">"Deblocați utilizatorul"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"A apărut o eroare la încercarea începerii conversației"</string>
|
|
||||||
<string name="screen_start_chat_unknown_profile">"Nu am putut valida ID-ul Matrix al acestui utilizator. Este posibil ca invitația să nu fi fost primită."</string>
|
|
||||||
<string name="settings_rageshake">"Rageshake"</string>
|
<string name="settings_rageshake">"Rageshake"</string>
|
||||||
<string name="settings_rageshake_detection_threshold">"Prag de detecție"</string>
|
<string name="settings_rageshake_detection_threshold">"Prag de detecție"</string>
|
||||||
<string name="settings_title_general">"General"</string>
|
<string name="settings_title_general">"General"</string>
|
||||||
<string name="settings_version_number">"Versiunea: %1$s (%2$s)"</string>
|
<string name="settings_version_number">"Versiunea: %1$s (%2$s)"</string>
|
||||||
<string name="test_language_identifier">"ro"</string>
|
<string name="test_language_identifier">"ro"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -181,6 +181,7 @@
|
||||||
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
|
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
|
||||||
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
|
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
|
||||||
<string name="room_timeline_read_marker_title">"New"</string>
|
<string name="room_timeline_read_marker_title">"New"</string>
|
||||||
|
<string name="screen_invites_empty_list">"No Invites"</string>
|
||||||
<string name="screen_invites_invited_you">"%1$s invited you"</string>
|
<string name="screen_invites_invited_you">"%1$s invited you"</string>
|
||||||
<string name="screen_report_content_block_user">"Block user"</string>
|
<string name="screen_report_content_block_user">"Block user"</string>
|
||||||
<string name="screen_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
|
<string name="screen_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
|
||||||
|
|
@ -190,8 +191,6 @@
|
||||||
<string name="screen_room_member_details_unblock_alert_action">"Unblock"</string>
|
<string name="screen_room_member_details_unblock_alert_action">"Unblock"</string>
|
||||||
<string name="screen_room_member_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
|
<string name="screen_room_member_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
|
||||||
<string name="screen_room_member_details_unblock_user">"Unblock user"</string>
|
<string name="screen_room_member_details_unblock_user">"Unblock user"</string>
|
||||||
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
|
||||||
<string name="screen_start_chat_unknown_profile">"We can’t validate this user’s Matrix ID. The invite might not be received."</string>
|
|
||||||
<string name="settings_rageshake">"Rageshake"</string>
|
<string name="settings_rageshake">"Rageshake"</string>
|
||||||
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
|
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
|
||||||
<string name="settings_title_general">"General"</string>
|
<string name="settings_title_general">"General"</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:aaeb60c55803711952413d99191209f467b4f77e1b03ad46601111691c2fc7fe
|
||||||
|
size 38188
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4c8ccd79709924e19793977ebe5ab4f6ded7f20507d9534dc24f46513fdd7a69
|
||||||
|
size 37844
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6b1243c92ee9f3735e81c2ab77910a7323b692a72c3c81c4b2ea776c1f0e5c84
|
||||||
|
size 16267
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7db235009f0e0a50eda1196f2cd24eb490af10ebe42e26cc73fdf8ea2fdb0bf8
|
||||||
|
size 15906
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:5abeb4bb717e33b34ff0a61d657a01b4e7ad965b5d7f8e4252c7e956c4caada3
|
||||||
|
size 38719
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:74c34325693f03c620a64493ea9bead27d8db1719d849ef4e1f589940efc73c9
|
||||||
|
size 34559
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2327de2be62afdacae32b297347fbcd23cd5e6986f513d018068aa981ecc3941
|
||||||
|
size 89757
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:50129ad0c4d75ff5e7b8ffd43b0ffdc40d77a78f5699f9b7194a9c9ef026af69
|
||||||
|
size 82189
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:365161fefa9ea3c82bf3ed2527b4847df27860266e1d5f0e770962e95154b4a6
|
oid sha256:6f36c2b4f4266048d5295df08f380e4128630d11cce7ac11cf3a0eaaa5594d61
|
||||||
size 19178
|
size 19924
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3f4413c59f47c45c06433f53c3f1fb7e9095bb47e3d8c3e7fabefcb19cdd5146
|
oid sha256:23b9e996b8f0cc2efcb3adb6294bfa7b4a53fefbb7b6ee07add4105da9b9d40e
|
||||||
size 18346
|
size 18984
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:247d303776a79f310144487784fb3841a430a6b0fa7fdee28485ed6157e42354
|
||||||
|
size 7056
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d93540b38b57540142d2e57e4ce2caf7424fcd2aa3017ab16449d60c1e156797
|
||||||
|
size 6730
|
||||||
|
|
@ -28,7 +28,8 @@
|
||||||
{
|
{
|
||||||
"name": ":features:createroom:impl",
|
"name": ":features:createroom:impl",
|
||||||
"includeRegex": [
|
"includeRegex": [
|
||||||
"screen_create_room_.*"
|
"screen_create_room_.*",
|
||||||
|
"screen_start_chat_.*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue