First Commit

This commit is contained in:
2025-12-18 16:28:50 +07:00
commit 8c3e4f491f
9974 changed files with 396488 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import extension.setupDependencyInjection
import extension.testCommonDependencies
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2022-2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-compose-library")
id("kotlin-parcelize")
}
android {
namespace = "io.element.android.features.createroom.impl"
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
}
setupDependencyInjection()
dependencies {
implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.deeplink.api)
implementation(projects.libraries.mediapickers.api)
implementation(projects.libraries.mediaupload.api)
implementation(projects.libraries.permissions.api)
implementation(projects.libraries.usersearch.impl)
implementation(projects.services.analytics.api)
implementation(libs.coil.compose)
implementation(projects.libraries.featureflag.api)
implementation(projects.features.invitepeople.api)
api(projects.features.createroom.api)
testCommonDependencies(libs, true)
testImplementation(projects.services.analytics.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.mediapickers.test)
testImplementation(projects.libraries.mediaupload.test)
testImplementation(projects.libraries.permissions.test)
testImplementation(projects.libraries.usersearch.test)
testImplementation(projects.features.startchat.test)
testImplementation(projects.libraries.featureflag.test)
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl
import android.os.Parcelable
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 com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.replace
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.createroom.impl.addpeople.AddPeopleNode
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode
import io.element.android.libraries.architecture.BackstackView
import io.element.android.libraries.architecture.BaseFlowNode
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.parcelize.Parcelize
@ContributesNode(SessionScope::class)
@AssistedInject
class CreateRoomFlowNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
) : BaseFlowNode<CreateRoomFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.ConfigureRoom,
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
plugins = plugins
) {
private val callback: CreateRoomEntryPoint.Callback = callback()
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.ConfigureRoom -> {
val callback = object : ConfigureRoomNode.Callback {
override fun onCreateRoomSuccess(roomId: RoomId) {
backstack.replace(NavTarget.AddPeople(roomId))
}
}
createNode<ConfigureRoomNode>(buildContext, plugins = listOf(callback))
}
is NavTarget.AddPeople -> {
val inputs = AddPeopleNode.Inputs(navTarget.roomId)
val callback: AddPeopleNode.Callback = object : AddPeopleNode.Callback {
override fun onFinish() {
callback.onRoomCreated(navTarget.roomId)
}
}
createNode<AddPeopleNode>(buildContext, plugins = listOf(inputs, callback))
}
}
}
@Composable
override fun View(modifier: Modifier) {
BackstackView()
}
sealed interface NavTarget : Parcelable {
@Parcelize
data object ConfigureRoom : NavTarget
@Parcelize
data class AddPeople(val roomId: RoomId) : NavTarget
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.SessionScope
@ContributesBinding(SessionScope::class)
class DefaultCreateRoomEntryPoint : CreateRoomEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
callback: CreateRoomEntryPoint.Callback,
): Node {
return parentNode.createNode<CreateRoomFlowNode>(buildContext, listOf(callback))
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.addpeople
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 dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.features.invitepeople.api.InvitePeoplePresenter
import io.element.android.features.invitepeople.api.InvitePeopleRenderer
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
@ContributesNode(SessionScope::class)
@AssistedInject
class AddPeopleNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
invitePeoplePresenterFactory: InvitePeoplePresenter.Factory,
private val invitePeopleRenderer: InvitePeopleRenderer,
) : Node(buildContext, plugins = plugins) {
data class Inputs(
val roomId: RoomId,
) : NodeInputs
interface Callback : Plugin {
fun onFinish()
}
private val callback: Callback = callback()
private val roomId = inputs<Inputs>().roomId
private val invitePeoplePresenter = invitePeoplePresenterFactory.create(
joinedRoom = null,
roomId = roomId,
)
@Composable
override fun View(modifier: Modifier) {
val state = invitePeoplePresenter.present()
AddPeopleView(
state = state,
onFinish = callback::onFinish,
) {
invitePeopleRenderer.Render(state, Modifier)
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.addpeople
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.features.createroom.impl.R
import io.element.android.features.invitepeople.api.InvitePeopleEvents
import io.element.android.features.invitepeople.api.InvitePeopleState
import io.element.android.features.invitepeople.api.InvitePeopleStateProvider
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun AddPeopleView(
state: InvitePeopleState,
onFinish: () -> Unit,
modifier: Modifier = Modifier,
invitePeopleView: @Composable () -> Unit,
) {
HeaderFooterPage(
modifier = modifier,
contentPadding = PaddingValues(0.dp),
topBar = {
AddPeopleTopBar(onSkipClick = onFinish)
},
footer = {
Button(
text = stringResource(CommonStrings.action_finish),
onClick = {
state.eventSink(InvitePeopleEvents.SendInvites)
onFinish()
},
enabled = state.canInvite,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
)
},
content = invitePeopleView
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AddPeopleTopBar(
onSkipClick: () -> Unit,
) {
TopAppBar(
titleStr = stringResource(R.string.screen_create_room_add_people_title),
actions = {
TextButton(
text = stringResource(CommonStrings.action_skip),
onClick = onSkipClick,
)
}
)
}
@PreviewsDayNight
@Composable
internal fun AddPeopleViewPreview(@PreviewParameter(InvitePeopleStateProvider::class) state: InvitePeopleState) = ElementPreview {
AddPeopleView(
state = state,
invitePeopleView = {},
onFinish = {},
)
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
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 RoomVisibilityChanged(val visibilityItem: RoomVisibilityItem) : ConfigureRoomEvents
data class RoomAccessChanged(val roomAccess: RoomAccessItem) : ConfigureRoomEvents
data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents
data object CreateRoom : ConfigureRoomEvents
data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents
data object CancelCreateRoom : ConfigureRoomEvents
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.lifecycle.subscribe
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.annotations.ContributesNode
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.services.analytics.api.AnalyticsService
@ContributesNode(SessionScope::class)
@AssistedInject
class ConfigureRoomNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: ConfigureRoomPresenter,
private val analyticsService: AnalyticsService,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onCreateRoomSuccess(roomId: RoomId)
}
init {
lifecycle.subscribe(
onResume = {
analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.CreateRoom))
}
)
}
private val callback: Callback = callback()
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
ConfigureRoomView(
state = state,
modifier = modifier,
onBackClick = this::navigateUp,
onCreateRoomSuccess = callback::onCreateRoomSuccess,
)
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.core.net.toUri
import dev.zacsweers.metro.Inject
import im.vector.app.features.analytics.plan.CreatedRoom
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEffect
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.jvm.optionals.getOrDefault
@Inject
class ConfigureRoomPresenter(
private val dataStore: CreateRoomConfigStore,
private val matrixClient: MatrixClient,
private val mediaPickerProvider: PickerProvider,
private val mediaPreProcessor: MediaPreProcessor,
private val analyticsService: AnalyticsService,
permissionsPresenterFactory: PermissionsPresenter.Factory,
private val featureFlagService: FeatureFlagService,
private val roomAliasHelper: RoomAliasHelper,
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
) : Presenter<ConfigureRoomState> {
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
private var pendingPermissionRequest = false
@Composable
override fun present(): ConfigureRoomState {
val cameraPermissionState = cameraPermissionPresenter.present()
val createRoomConfig by dataStore.getCreateRoomConfigFlow().collectAsState(CreateRoomConfig())
val homeserverName = remember { matrixClient.userIdServerName() }
val isKnockFeatureEnabled by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
}.collectAsState(initial = false)
val roomAddressValidity = remember {
mutableStateOf<RoomAddressValidity>(RoomAddressValidity.Unknown)
}
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) },
)
val galleryImagePicker = mediaPickerProvider.registerGalleryImagePicker(
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri) }
)
val avatarActions by remember(createRoomConfig.avatarUri) {
derivedStateOf {
listOfNotNull(
AvatarAction.TakePhoto,
AvatarAction.ChoosePhoto,
AvatarAction.Remove.takeIf { createRoomConfig.avatarUri != null },
).toImmutableList()
}
}
LaunchedEffect(cameraPermissionState.permissionGranted) {
if (cameraPermissionState.permissionGranted && pendingPermissionRequest) {
pendingPermissionRequest = false
cameraPhotoPicker.launch()
}
}
RoomAddressValidityEffect(
client = matrixClient,
roomAliasHelper = roomAliasHelper,
newRoomAddress = createRoomConfig.roomVisibility.roomAddress().getOrDefault(""),
knownRoomAddress = null,
) { newRoomAddressValidity ->
roomAddressValidity.value = newRoomAddressValidity
}
val localCoroutineScope = rememberCoroutineScope()
val createRoomAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
fun createRoom(config: CreateRoomConfig) {
createRoomAction.value = AsyncAction.Uninitialized
localCoroutineScope.createRoom(config, createRoomAction)
}
fun handleEvent(event: ConfigureRoomEvents) {
when (event) {
is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name)
is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic)
is ConfigureRoomEvents.RoomVisibilityChanged -> dataStore.setRoomVisibility(event.visibilityItem)
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig)
is ConfigureRoomEvents.HandleAvatarAction -> {
when (event.action) {
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
AvatarAction.TakePhoto -> if (cameraPermissionState.permissionGranted) {
cameraPhotoPicker.launch()
} else {
pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
}
AvatarAction.Remove -> dataStore.setAvatarUri(uri = null)
}
}
ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = AsyncAction.Uninitialized
}
}
return ConfigureRoomState(
isKnockFeatureEnabled = isKnockFeatureEnabled,
config = createRoomConfig,
avatarActions = avatarActions,
createRoomAction = createRoomAction.value,
cameraPermissionState = cameraPermissionState,
homeserverName = homeserverName,
roomAddressValidity = roomAddressValidity.value,
eventSink = ::handleEvent,
)
}
private fun CoroutineScope.createRoom(
config: CreateRoomConfig,
createRoomAction: MutableState<AsyncAction<RoomId>>
) = launch {
suspend {
val avatarUrl = config.avatarUri?.let { uploadAvatar(it.toUri()) }
val params = if (config.roomVisibility is RoomVisibilityState.Public) {
CreateRoomParameters(
name = config.roomName,
topic = config.topic,
isEncrypted = false,
isDirect = false,
visibility = RoomVisibility.Public,
joinRuleOverride = config.roomVisibility.roomAccess.toJoinRule(),
preset = RoomPreset.PUBLIC_CHAT,
invite = config.invites.map { it.userId },
avatar = avatarUrl,
roomAliasName = config.roomVisibility.roomAddress()
)
} else {
CreateRoomParameters(
name = config.roomName,
topic = config.topic,
isEncrypted = config.roomVisibility is RoomVisibilityState.Private,
isDirect = false,
visibility = RoomVisibility.Private,
historyVisibilityOverride = RoomHistoryVisibility.Invited,
preset = RoomPreset.PRIVATE_CHAT,
invite = config.invites.map { it.userId },
avatar = avatarUrl,
)
}
matrixClient.createRoom(params)
.onFailure { failure ->
Timber.e(failure, "Failed to create room")
}
.onSuccess {
dataStore.clearCachedData()
analyticsService.capture(CreatedRoom(isDM = false))
}
.getOrThrow()
}.runCatchingUpdatingState(createRoomAction)
}
private suspend fun uploadAvatar(avatarUri: Uri): String {
val preprocessed = mediaPreProcessor.process(
uri = avatarUri,
mimeType = MimeTypes.Jpeg,
deleteOriginal = false,
mediaOptimizationConfig = mediaOptimizationConfigProvider.get(),
).getOrThrow()
val byteArray = preprocessed.file.readBytes()
return matrixClient.uploadMedia(MimeTypes.Jpeg, byteArray).getOrThrow()
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.permissions.api.PermissionsState
import kotlinx.collections.immutable.ImmutableList
data class ConfigureRoomState(
val isKnockFeatureEnabled: Boolean,
val config: CreateRoomConfig,
val avatarActions: ImmutableList<AvatarAction>,
val createRoomAction: AsyncAction<RoomId>,
val cameraPermissionState: PermissionsState,
val roomAddressValidity: RoomAddressValidity,
val homeserverName: String,
val eventSink: (ConfigureRoomEvents) -> Unit
) {
val isValid: Boolean = config.roomName?.isNotEmpty() == true &&
(config.roomVisibility is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid)
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.permissions.api.PermissionsState
import io.element.android.libraries.permissions.api.aPermissionsState
import kotlinx.collections.immutable.toImmutableList
open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomState> {
override val values: Sequence<ConfigureRoomState>
get() = sequenceOf(
aConfigureRoomState(),
aConfigureRoomState(
isKnockFeatureEnabled = false,
config = CreateRoomConfig(
roomName = "Room 101",
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldnt be more than 3 lines",
invites = aMatrixUserList().toImmutableList(),
roomVisibility = RoomVisibilityState.Public(
roomAddress = RoomAddress.AutoFilled("Room-101"),
roomAccess = RoomAccess.Knocking,
),
),
),
aConfigureRoomState(
config = CreateRoomConfig(
roomName = "Room 101",
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldnt be more than 3 lines",
invites = aMatrixUserList().toImmutableList(),
roomVisibility = RoomVisibilityState.Public(
roomAddress = RoomAddress.AutoFilled("Room-101"),
roomAccess = RoomAccess.Knocking,
),
),
),
aConfigureRoomState(
config = CreateRoomConfig(
roomName = "Room 101",
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldnt be more than 3 lines",
roomVisibility = RoomVisibilityState.Public(
roomAddress = RoomAddress.AutoFilled("Room-101"),
roomAccess = RoomAccess.Knocking,
),
),
roomAddressValidity = RoomAddressValidity.NotAvailable,
),
aConfigureRoomState(
config = CreateRoomConfig(
roomName = "Room 101",
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldnt be more than 3 lines",
roomVisibility = RoomVisibilityState.Public(
roomAddress = RoomAddress.AutoFilled("Room-101"),
roomAccess = RoomAccess.Knocking,
),
),
roomAddressValidity = RoomAddressValidity.InvalidSymbols,
),
aConfigureRoomState(
config = CreateRoomConfig(
roomName = "Room 101",
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldnt be more than 3 lines",
roomVisibility = RoomVisibilityState.Public(
roomAddress = RoomAddress.AutoFilled("Room-101"),
roomAccess = RoomAccess.Knocking,
),
),
roomAddressValidity = RoomAddressValidity.Valid,
),
)
}
fun aConfigureRoomState(
config: CreateRoomConfig = CreateRoomConfig(),
isKnockFeatureEnabled: Boolean = true,
avatarActions: List<AvatarAction> = emptyList(),
createRoomAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false),
homeserverName: String = "matrix.org",
roomAddressValidity: RoomAddressValidity = RoomAddressValidity.Valid,
eventSink: (ConfigureRoomEvents) -> Unit = { },
) = ConfigureRoomState(
config = config,
isKnockFeatureEnabled = isKnockFeatureEnabled,
avatarActions = avatarActions.toImmutableList(),
createRoomAction = createRoomAction,
cameraPermissionState = cameraPermissionState,
homeserverName = homeserverName,
roomAddressValidity = roomAddressValidity,
eventSink = eventSink,
)

View File

@@ -0,0 +1,337 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.createroom.impl.R
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
import io.element.android.libraries.matrix.ui.room.address.RoomAddressField
import io.element.android.libraries.permissions.api.PermissionsView
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun ConfigureRoomView(
state: ConfigureRoomState,
onBackClick: () -> Unit,
onCreateRoomSuccess: (RoomId) -> Unit,
modifier: Modifier = Modifier,
) {
val focusManager = LocalFocusManager.current
val isAvatarActionsSheetVisible = remember { mutableStateOf(false) }
fun onAvatarClick() {
focusManager.clearFocus()
isAvatarActionsSheetVisible.value = true
}
Scaffold(
modifier = modifier.clearFocusOnTap(focusManager),
topBar = {
ConfigureRoomToolbar(
isNextActionEnabled = state.isValid,
onBackClick = onBackClick,
onNextClick = {
focusManager.clearFocus()
state.eventSink(ConfigureRoomEvents.CreateRoom)
},
)
}
) { padding ->
Column(
modifier = Modifier
.padding(padding)
.imePadding()
.verticalScroll(rememberScrollState())
.consumeWindowInsets(padding),
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
RoomNameWithAvatar(
modifier = Modifier.padding(horizontal = 16.dp),
avatarUri = state.config.avatarUri,
roomName = state.config.roomName.orEmpty(),
onAvatarClick = ::onAvatarClick,
onChangeRoomName = { state.eventSink(ConfigureRoomEvents.RoomNameChanged(it)) },
)
RoomTopic(
modifier = Modifier.padding(horizontal = 16.dp),
topic = state.config.topic.orEmpty(),
onTopicChange = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) },
)
RoomVisibilityOptions(
selected = when (state.config.roomVisibility) {
is RoomVisibilityState.Private -> RoomVisibilityItem.Private
is RoomVisibilityState.Public -> RoomVisibilityItem.Public
},
onOptionClick = {
focusManager.clearFocus()
state.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(it))
},
)
if (state.config.roomVisibility is RoomVisibilityState.Public && state.isKnockFeatureEnabled) {
RoomAccessOptions(
selected = when (state.config.roomVisibility.roomAccess) {
RoomAccess.Anyone -> RoomAccessItem.Anyone
RoomAccess.Knocking -> RoomAccessItem.AskToJoin
},
onOptionClick = {
focusManager.clearFocus()
state.eventSink(ConfigureRoomEvents.RoomAccessChanged(it))
},
)
RoomAddressField(
modifier = Modifier.padding(horizontal = 16.dp),
address = state.config.roomVisibility.roomAddress.value,
homeserverName = state.homeserverName,
addressValidity = state.roomAddressValidity,
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
label = stringResource(R.string.screen_create_room_room_address_section_title),
supportingText = stringResource(R.string.screen_create_room_room_address_section_footer),
)
Spacer(Modifier)
}
}
}
AvatarActionBottomSheet(
actions = state.avatarActions,
isVisible = isAvatarActionsSheetVisible.value,
onDismiss = { isAvatarActionsSheetVisible.value = false },
onSelectAction = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
)
AsyncActionView(
async = state.createRoomAction,
progressDialog = {
AsyncActionViewDefaults.ProgressDialog(
progressText = stringResource(CommonStrings.common_creating_room),
)
},
onSuccess = { onCreateRoomSuccess(it) },
errorMessage = { stringResource(R.string.screen_create_room_error_creating_room) },
onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom) },
onErrorDismiss = { state.eventSink(ConfigureRoomEvents.CancelCreateRoom) },
)
PermissionsView(
state = state.cameraPermissionState,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ConfigureRoomToolbar(
isNextActionEnabled: Boolean,
onBackClick: () -> Unit,
onNextClick: () -> Unit,
) {
TopAppBar(
titleStr = stringResource(R.string.screen_create_room_title),
navigationIcon = { BackButton(onClick = onBackClick) },
actions = {
TextButton(
text = stringResource(CommonStrings.action_create),
enabled = isNextActionEnabled,
onClick = onNextClick,
)
}
)
}
@Composable
private fun RoomNameWithAvatar(
avatarUri: String?,
roomName: String,
onAvatarClick: () -> Unit,
onChangeRoomName: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
val a11yAvatar = stringResource(CommonStrings.a11y_room_avatar)
UnsavedAvatar(
avatarUri = avatarUri,
avatarSize = AvatarSize.EditRoomDetails,
avatarType = AvatarType.Room(),
modifier = Modifier
.clickable(
onClick = onAvatarClick,
onClickLabel = stringResource(CommonStrings.action_open_context_menu),
)
.clearAndSetSemantics {
contentDescription = a11yAvatar
},
)
TextField(
label = stringResource(R.string.screen_create_room_room_name_label),
value = roomName,
placeholder = stringResource(CommonStrings.common_room_name_placeholder),
singleLine = true,
onValueChange = onChangeRoomName,
)
}
}
@Composable
private fun RoomTopic(
topic: String,
onTopicChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
TextField(
modifier = modifier,
label = stringResource(R.string.screen_create_room_topic_label),
value = topic,
onValueChange = onTopicChange,
maxLines = 3,
supportingText = stringResource(CommonStrings.common_topic_placeholder),
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences,
),
)
}
@Composable
private fun ConfigureRoomOptions(
title: String,
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
) {
Column(
modifier = modifier.selectableGroup()
) {
Text(
text = title,
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textPrimary,
modifier = Modifier.padding(horizontal = 16.dp),
)
content()
}
}
@Composable
private fun RoomVisibilityOptions(
selected: RoomVisibilityItem,
onOptionClick: (RoomVisibilityItem) -> Unit,
modifier: Modifier = Modifier,
) {
ConfigureRoomOptions(
title = stringResource(R.string.screen_create_room_room_visibility_section_title),
modifier = modifier,
) {
RoomVisibilityItem.entries.forEach { item ->
val isSelected = item == selected
ListItem(
leadingContent = ListItemContent.Custom {
RoundedIconAtom(
size = RoundedIconAtomSize.Big,
resourceId = item.icon,
tint = if (isSelected) ElementTheme.colors.iconPrimary else ElementTheme.colors.iconSecondary,
)
},
headlineContent = { Text(text = stringResource(item.title)) },
supportingContent = { Text(text = stringResource(item.description)) },
trailingContent = ListItemContent.RadioButton(selected = isSelected),
onClick = { onOptionClick(item) },
)
}
}
}
@Composable
private fun RoomAccessOptions(
selected: RoomAccessItem,
onOptionClick: (RoomAccessItem) -> Unit,
modifier: Modifier = Modifier,
) {
ConfigureRoomOptions(
title = stringResource(R.string.screen_create_room_room_access_section_header),
modifier = modifier,
) {
RoomAccessItem.entries.forEach { item ->
ListItem(
headlineContent = { Text(text = stringResource(item.title)) },
supportingContent = { Text(text = stringResource(item.description)) },
trailingContent = ListItemContent.RadioButton(selected = item == selected),
onClick = { onOptionClick(item) },
)
}
}
}
@PreviewWithLargeHeight
@Composable
internal fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
ElementPreviewLight { ContentToPreview(state) }
@PreviewWithLargeHeight
@Composable
internal fun ConfigureRoomViewDarkPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
ElementPreviewDark { ContentToPreview(state) }
@ExcludeFromCoverage
@Composable
private fun ContentToPreview(state: ConfigureRoomState) {
ConfigureRoomView(
state = state,
onBackClick = {},
onCreateRoomSuccess = {},
)
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
data class CreateRoomConfig(
val roomName: String? = null,
val topic: String? = null,
val avatarUri: String? = null,
val invites: ImmutableList<MatrixUser> = persistentListOf(),
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
)

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import android.net.Uri
import dev.zacsweers.metro.Inject
import io.element.android.libraries.androidutils.file.safeDelete
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.getAndUpdate
import java.io.File
@Inject
class CreateRoomConfigStore(
private val roomAliasHelper: RoomAliasHelper,
) {
private val createRoomConfigFlow: MutableStateFlow<CreateRoomConfig> = MutableStateFlow(CreateRoomConfig())
private var cachedAvatarUri: Uri? = null
set(value) {
field?.path?.let { File(it) }?.safeDelete()
field = value
}
fun getCreateRoomConfigFlow(): StateFlow<CreateRoomConfig> = createRoomConfigFlow
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()) {
val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(roomName)
config.roomVisibility.copy(
roomAddress = RoomAddress.AutoFilled(roomAliasName),
)
} else {
config.roomVisibility
}
}
else -> config.roomVisibility
}
config.copy(
roomName = roomName.takeIf { it.isNotEmpty() },
roomVisibility = newVisibility,
)
}
}
fun setTopic(topic: String) {
createRoomConfigFlow.getAndUpdate { config ->
config.copy(topic = topic.takeIf { it.isNotEmpty() })
}
}
fun setAvatarUri(uri: Uri?, cached: Boolean = false) {
cachedAvatarUri = uri.takeIf { cached }
createRoomConfigFlow.getAndUpdate { config ->
config.copy(avatarUri = uri?.toString())
}
}
fun setRoomVisibility(visibility: RoomVisibilityItem) {
createRoomConfigFlow.getAndUpdate { config ->
config.copy(
roomVisibility = when (visibility) {
RoomVisibilityItem.Private -> RoomVisibilityState.Private
RoomVisibilityItem.Public -> {
val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(config.roomName.orEmpty())
RoomVisibilityState.Public(
roomAddress = RoomAddress.AutoFilled(roomAliasName),
roomAccess = RoomAccess.Anyone,
)
}
}
)
}
}
fun setRoomAddress(address: String) {
createRoomConfigFlow.getAndUpdate { config ->
config.copy(
roomVisibility = when (config.roomVisibility) {
is RoomVisibilityState.Public -> {
val sanitizedAddress = address.lowercase()
config.roomVisibility.copy(roomAddress = RoomAddress.Edited(sanitizedAddress))
}
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() {
cachedAvatarUri = null
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import io.element.android.libraries.matrix.api.room.join.JoinRule
enum class RoomAccess {
Anyone,
Knocking
}
fun RoomAccess.toJoinRule(): JoinRule? {
return when (this) {
RoomAccess.Anyone -> null
RoomAccess.Knocking -> JoinRule.Knock
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import androidx.annotation.StringRes
import io.element.android.features.createroom.impl.R
enum class RoomAccessItem(
@StringRes val title: Int,
@StringRes val description: Int
) {
Anyone(
title = R.string.screen_create_room_room_access_section_anyone_option_title,
description = R.string.screen_create_room_room_access_section_anyone_option_description,
),
AskToJoin(
title = R.string.screen_create_room_room_access_section_knocking_option_title,
description = R.string.screen_create_room_room_access_section_knocking_option_description,
),
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
sealed class RoomAddress(open val value: String) {
data class AutoFilled(override val value: String) : RoomAddress(value)
data class Edited(override val value: String) : RoomAddress(value)
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import io.element.android.features.createroom.impl.R
import io.element.android.libraries.designsystem.icons.CompoundDrawables
enum class RoomVisibilityItem(
@DrawableRes val icon: Int,
@StringRes val title: Int,
@StringRes val description: Int
) {
Private(
icon = CompoundDrawables.ic_compound_lock,
title = R.string.screen_create_room_private_option_title,
description = R.string.screen_create_room_private_option_description,
),
Public(
icon = CompoundDrawables.ic_compound_public,
title = R.string.screen_create_room_public_option_title,
description = R.string.screen_create_room_public_option_description,
)
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl.configureroom
import java.util.Optional
sealed interface RoomVisibilityState {
data object Private : RoomVisibilityState
data class Public(
val roomAddress: RoomAddress,
val roomAccess: RoomAccess,
) : RoomVisibilityState
fun roomAddress(): Optional<String> {
return when (this) {
is Private -> Optional.empty()
is Public -> Optional.of(roomAddress.value)
}
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Новы пакой"</string>
<string name="screen_create_room_add_people_title">"Запрасіць карыстальнікаў"</string>
<string name="screen_create_room_error_creating_room">"Пры стварэнні пакоя адбылася памылка"</string>
<string name="screen_create_room_private_option_description">"Толькі запрошаныя людзі могуць атрымаць доступ да гэтага пакоя. Усе паведамленні абаронены end-to-end шыфраваннем."</string>
<string name="screen_create_room_private_option_title">"Прыватны пакой"</string>
<string name="screen_create_room_public_option_description">"Любы можа знайсці гэты пакой.
Вы можаце змяніць гэта ў любы час у наладах пакоя."</string>
<string name="screen_create_room_public_option_title">"Публічны пакой"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Хто заўгодна"</string>
<string name="screen_create_room_room_access_section_header">"Доступ у пакой"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Папрасіце далучыцца"</string>
<string name="screen_create_room_room_name_label">"Назва пакоя"</string>
<string name="screen_create_room_title">"Стварыце пакой"</string>
<string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Нова стая"</string>
<string name="screen_create_room_add_people_title">"Поканване на хора"</string>
<string name="screen_create_room_error_creating_room">"Възникна грешка при създаването на стаята"</string>
<string name="screen_create_room_private_option_description">"Само поканени хора имат достъп до тази стая. Всички съобщения са шифровани от край до край."</string>
<string name="screen_create_room_private_option_title">"Частна стая"</string>
<string name="screen_create_room_public_option_description">"Всеки може да намери тази стая.
Можете да промените това по всяко време в настройките на стаята."</string>
<string name="screen_create_room_public_option_title">"Общодостъпна стая"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Всеки може да се присъедини към тази стая"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Всеки"</string>
<string name="screen_create_room_room_address_section_footer">"За да бъде тази стая видима в директорията на общодостъпните стаи, ще ви е необходим адрес на стаята."</string>
<string name="screen_create_room_room_name_label">"Име на стаята"</string>
<string name="screen_create_room_room_visibility_section_title">"Видимост на стаята"</string>
<string name="screen_create_room_title">"Създаване на стая"</string>
<string name="screen_create_room_topic_label">"Тема за разговор (незадължително)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nová místnost"</string>
<string name="screen_create_room_add_people_title">"Pozvat přátele"</string>
<string name="screen_create_room_error_creating_room">"Při vytváření místnosti došlo k chybě"</string>
<string name="screen_create_room_private_option_description">"Do této místnosti mají přístup pouze pozvaní lidé. Všechny zprávy jsou koncově šifrovány."</string>
<string name="screen_create_room_private_option_title">"Soukromá místnost"</string>
<string name="screen_create_room_public_option_description">"Tuto místnost může najít kdokoli.
To můžete kdykoli změnit v nastavení místnosti."</string>
<string name="screen_create_room_public_option_title">"Veřejná místnost"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Do této místnosti může vstoupit kdokoli"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Kdokoliv"</string>
<string name="screen_create_room_room_access_section_header">"Přístup do místnosti"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Kdokoli může požádat o vstup do místnosti, ale správce nebo moderátor bude muset žádost přijmout"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Požádat o připojení"</string>
<string name="screen_create_room_room_address_section_footer">"Aby byla tato místnost viditelná v adresáři veřejných místností, budete potřebovat adresu místnosti."</string>
<string name="screen_create_room_room_address_section_title">"Adresa místnosti"</string>
<string name="screen_create_room_room_name_label">"Název místnosti"</string>
<string name="screen_create_room_room_visibility_section_title">"Viditelnost místnosti"</string>
<string name="screen_create_room_title">"Vytvořit místnost"</string>
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Ystafell newydd"</string>
<string name="screen_create_room_add_people_title">"Gwahodd pobl"</string>
<string name="screen_create_room_error_creating_room">"Bu gwall wrth greu\'r ystafell"</string>
<string name="screen_create_room_private_option_description">"Dim ond pobl wahoddwyd all gael mynediad i\'r ystafell hon. Mae pob neges wedi\'i hamgryptio o\'r dechrau i\'r diwedd."</string>
<string name="screen_create_room_private_option_title">"Ystafell breifat"</string>
<string name="screen_create_room_public_option_description">"Gall unrhyw un ddod o hyd i\'r ystafell hon.
Gallwch newid hyn unrhyw bryd yng ngosodiadau ystafell."</string>
<string name="screen_create_room_public_option_title">"Ystafell gyhoeddus"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Gall unrhyw un ymuno â\'r ystafell hon"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Unrhyw un"</string>
<string name="screen_create_room_room_access_section_header">"Mynediad i\'r Ystafell"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Gall unrhyw un ofyn am gael ymuno â\'r ystafell ond bydd rhaid i weinyddwr neu gymedrolwr dderbyn y cais"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Gofyn i gael ymuno"</string>
<string name="screen_create_room_room_address_section_footer">"Er mwyn i\'r ystafell hon fod yn weladwy yn y cyfeiriadur ystafelloedd cyhoeddus, bydd angen cyfeiriad ystafell arnoch."</string>
<string name="screen_create_room_room_address_section_title">"Cyfeiriad yr ystafell"</string>
<string name="screen_create_room_room_name_label">"Enw\'r ystafell"</string>
<string name="screen_create_room_room_visibility_section_title">"Gwelededd yr ystafell"</string>
<string name="screen_create_room_title">"Creu ystafell"</string>
<string name="screen_create_room_topic_label">"Pwnc (dewisol)"</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nyt rum"</string>
<string name="screen_create_room_add_people_title">"Invitér andre"</string>
<string name="screen_create_room_error_creating_room">"Der opstod en fejl ved oprettelsen af rummet"</string>
<string name="screen_create_room_private_option_description">"Kun inviterede personer kan få adgang til dette rum. Alle meddelelser er ende-til-ende krypteret."</string>
<string name="screen_create_room_private_option_title">"Privat rum"</string>
<string name="screen_create_room_public_option_description">"Alle kan finde dette rum.
Du kan ændre dette når som helst i rummets indstillinger."</string>
<string name="screen_create_room_public_option_title">"Offentligt rum"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Alle kan deltage i dette rum"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Enhver"</string>
<string name="screen_create_room_room_access_section_header">"Adgang til rummet"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan bede om at deltage i rummet, men en administrator eller en moderator skal acceptere anmodningen"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Spørg om at deltage"</string>
<string name="screen_create_room_room_address_section_footer">"Hvis dette rum skal være synligt i det offentlige register, skal du bruge en rum-adresse."</string>
<string name="screen_create_room_room_name_label">"Navn på rum"</string>
<string name="screen_create_room_room_visibility_section_title">"Rummets synlighed"</string>
<string name="screen_create_room_title">"Opret et rum"</string>
<string name="screen_create_room_topic_label">"Emne (valgfrit)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Neuer Chat"</string>
<string name="screen_create_room_add_people_title">"Nutzer einladen"</string>
<string name="screen_create_room_error_creating_room">"Beim Erstellen des Chats ist ein Fehler aufgetreten"</string>
<string name="screen_create_room_private_option_description">"Nur eingeladene Personen haben Zutritt zu diesem Chat. Alle Nachrichten sind Ende-zu-Ende verschlüsselt."</string>
<string name="screen_create_room_private_option_title">"Privater Chat"</string>
<string name="screen_create_room_public_option_description">"Jeder kann diesen Chat finden.
Du kannst dies jederzeit in den Einstellungen des Chats ändern."</string>
<string name="screen_create_room_public_option_title">"Öffentlicher Chat"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Jeder darf diesem Chat beitreten"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Jeder"</string>
<string name="screen_create_room_room_access_section_header">"Chat Zugang"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Jeder kann den Beitritt zum Chat erbitten, aber ein Admin oder Moderator muss die Anfrage akzeptieren."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Beitritt beantragen"</string>
<string name="screen_create_room_room_address_section_footer">"Du benötigst eine Chat-Adresse, damit dieser Chat im öffentlichen Verzeichnis sichtbar ist."</string>
<string name="screen_create_room_room_address_section_title">"Chatroom Adresse"</string>
<string name="screen_create_room_room_name_label">"Chat-Name"</string>
<string name="screen_create_room_room_visibility_section_title">" Sichtbarkeit des Chats"</string>
<string name="screen_create_room_title">"Chat erstellen"</string>
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Νέα αίθουσα"</string>
<string name="screen_create_room_add_people_title">"Πρόσκληση ατόμων"</string>
<string name="screen_create_room_error_creating_room">"Προέκυψε σφάλμα κατά τη δημιουργία της αίθουσας"</string>
<string name="screen_create_room_private_option_description">"Μόνο τα άτομα που έχουν προσκληθεί μπορούν να έχουν πρόσβαση σε αυτή την αίθουσα. Όλα τα μηνύματα είναι κρυπτογραφημένα από άκρο σε άκρο."</string>
<string name="screen_create_room_private_option_title">"Ιδιωτική αίθουσα"</string>
<string name="screen_create_room_public_option_description">"Ο καθένας μπορεί να βρει αυτή την αίθουσα.
Αυτό μπορείτε να το αλλάξετε ανά πάσα στιγμή στις ρυθμίσεις της αίθουσας."</string>
<string name="screen_create_room_public_option_title">"Δημόσια αίθουσα"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Οποιοσδήποτε μπορεί να συμμετάσχει σε αυτή την αίθουσα"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Οποιοσδήποτε"</string>
<string name="screen_create_room_room_access_section_header">"Πρόσβαση στην Αίθουσα"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στην αίθουσα, αλλά ένας διαχειριστής ή ένας συντονιστής θα πρέπει να αποδεχτεί το αίτημα"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Αίτημα συμμετοχής"</string>
<string name="screen_create_room_room_address_section_footer">"Για να είναι ορατή αυτή η αίθουσα στον δημόσιο κατάλογο αιθουσών, θα χρειαστείτε μια διεύθυνση αίθουσας."</string>
<string name="screen_create_room_room_address_section_title">"Διεύθυνση δωματίου"</string>
<string name="screen_create_room_room_name_label">"Όνομα αίθουσας"</string>
<string name="screen_create_room_room_visibility_section_title">"Ορατότητα αίθουσας"</string>
<string name="screen_create_room_title">"Δημιουργία αίθουσας"</string>
<string name="screen_create_room_topic_label">"Θέμα (προαιρετικό)"</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nueva sala"</string>
<string name="screen_create_room_add_people_title">"Invitar personas"</string>
<string name="screen_create_room_error_creating_room">"Se ha producido un error al crear la sala"</string>
<string name="screen_create_room_private_option_description">"Solo las personas invitadas pueden acceder a esta sala. Todos los mensajes están cifrados de extremo a extremo."</string>
<string name="screen_create_room_private_option_title">"Sala privada"</string>
<string name="screen_create_room_public_option_description">"Cualquiera puede encontrar esta sala.
Puedes cambiar esto en cualquier momento en los ajustes de la sala."</string>
<string name="screen_create_room_public_option_title">"Sala pública"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Cualquiera puede unirse a esta sala"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Cualquiera"</string>
<string name="screen_create_room_room_access_section_header">"Acceso a la sala"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Cualquiera puede solicitar unirse a la sala, pero un administrador o un moderador tendrá que aceptar la solicitud"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Solicitud para unirse"</string>
<string name="screen_create_room_room_address_section_footer">"Para que esta sala sea visible en el directorio de salas públicas, necesitarás una dirección de sala."</string>
<string name="screen_create_room_room_name_label">"Nombre de la sala"</string>
<string name="screen_create_room_room_visibility_section_title">"Visibilidad de la sala"</string>
<string name="screen_create_room_title">"Crear una sala"</string>
<string name="screen_create_room_topic_label">"Tema (opcional)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Uus jututuba"</string>
<string name="screen_create_room_add_people_title">"Kutsu osalejaid"</string>
<string name="screen_create_room_error_creating_room">"Jututoa loomisel tekkis viga"</string>
<string name="screen_create_room_private_option_description">"Ligipääs siia jututuppa on vaid kutse alusel. Kõik sõnumid siin jututoas on läbivalt krüptitud."</string>
<string name="screen_create_room_private_option_title">"Privaatne jututuba"</string>
<string name="screen_create_room_public_option_description">"Kõik saavad seda jututuba leida.
Sa võid seda jututoa seadistustest alati muuta."</string>
<string name="screen_create_room_public_option_title">"Avalik jututuba"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Kõik võivad selle jututoaga liituda"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Kõik"</string>
<string name="screen_create_room_room_access_section_header">"Ligipääs jututoale"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Kõik võivad paluda selle jututoaga liitumist, kuid peakasutaja või moderaator peavad selle kinnitama"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Küsi võimalust liitumiseks"</string>
<string name="screen_create_room_room_address_section_footer">"Selleks, et see jututuba oleks nähtav jututubade avalikus kataloogis, sa vajad jututoa aadressi."</string>
<string name="screen_create_room_room_address_section_title">"Jututoa aadress"</string>
<string name="screen_create_room_room_name_label">"Jututoa nimi"</string>
<string name="screen_create_room_room_visibility_section_title">"Jututoa nähtavus"</string>
<string name="screen_create_room_title">"Loo jututuba"</string>
<string name="screen_create_room_topic_label">"Teema (kui soovid lisada)"</string>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Gela berria"</string>
<string name="screen_create_room_add_people_title">"Gonbidatu jendea"</string>
<string name="screen_create_room_error_creating_room">"Errorea gertatu da gela sortzean"</string>
<string name="screen_create_room_private_option_description">"Gonbidatutako jendea soilik sar daiteke gelara. Mezu guztiak daude ertzetik ertzera zifratuta."</string>
<string name="screen_create_room_private_option_title">"Gela pribatua"</string>
<string name="screen_create_room_public_option_description">"Edonork aurki dezake gela hau.
Gelaren ezarpenetan aldatu dezakezu hobespena."</string>
<string name="screen_create_room_public_option_title">"Gela publikoa"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Edonor sar daiteke gela honetara"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Edonork"</string>
<string name="screen_create_room_room_access_section_header">"Gelarako sarbidea"</string>
<string name="screen_create_room_room_address_section_title">"Gelaren helbidea"</string>
<string name="screen_create_room_room_name_label">"Gelaren izena"</string>
<string name="screen_create_room_room_visibility_section_title">"Gelaren ikusgarritasuna"</string>
<string name="screen_create_room_title">"Sortu gela"</string>
<string name="screen_create_room_topic_label">"Mintzagaia (aukerakoa)"</string>
</resources>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"اتاق جدید"</string>
<string name="screen_create_room_add_people_title">"دعوت افراد"</string>
<string name="screen_create_room_error_creating_room">"هنگام ایجاد اتاق خطایی رخ داد"</string>
<string name="screen_create_room_private_option_description">"تنها افراد دعوت شده می‌توانند به این اتاق دسترسی داشته باشند. همهٔ پیام‌ها رمزنگاری سرتاسری شده‌اند."</string>
<string name="screen_create_room_private_option_title">"اتاق خصوصی"</string>
<string name="screen_create_room_public_option_description">"هرکسی می‌تواند اتاق را بیابد.
می‌توانید بعداً در تظیمات اتاق عوضش کنید."</string>
<string name="screen_create_room_public_option_title">"اتاق عمومی"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"هرکسی می‌تواند به این اتاق بپیوندد"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"هرکسی"</string>
<string name="screen_create_room_room_access_section_header">"دسترسی اتاق"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"درخواست دعوت"</string>
<string name="screen_create_room_room_address_section_title">"نشانی اتاق"</string>
<string name="screen_create_room_room_name_label">"نام اتاق"</string>
<string name="screen_create_room_room_visibility_section_title">"نمایانی اتاق"</string>
<string name="screen_create_room_title">"ایجاد اتاق"</string>
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Uusi huone"</string>
<string name="screen_create_room_add_people_title">"Kutsu henkilöitä"</string>
<string name="screen_create_room_error_creating_room">"Huoneen luomisessa tapahtui virhe"</string>
<string name="screen_create_room_private_option_description">"Vain kutsutut henkilöt pääsevät tähän huoneeseen. Kaikki viestit ovat päästä päähän salattuja."</string>
<string name="screen_create_room_private_option_title">"Yksityinen huone"</string>
<string name="screen_create_room_public_option_description">"Kuka tahansa voi löytää tämän huoneen.
Voit muuttaa tämän milloin tahansa huoneen asetuksista."</string>
<string name="screen_create_room_public_option_title">"Julkinen huone"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Kuka tahansa voi liittyä tähän huoneeseen"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Kuka tahansa"</string>
<string name="screen_create_room_room_access_section_header">"Huoneeseen Pääsy"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Kuka tahansa voi pyytää saada liittyä huoneeseen, mutta ylläpitäjän tai valvojan on hyväksyttävä pyyntö"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Pyydä liittymistä"</string>
<string name="screen_create_room_room_address_section_footer">"Jotta tämä huone näkyisi julkisessa huonehakemistossa, tarvitset huoneen osoitteen."</string>
<string name="screen_create_room_room_address_section_title">"Huoneen osoite"</string>
<string name="screen_create_room_room_name_label">"Huoneen nimi"</string>
<string name="screen_create_room_room_visibility_section_title">"Huoneen näkyvyys"</string>
<string name="screen_create_room_title">"Luo huone"</string>
<string name="screen_create_room_topic_label">"Aihe (valinnainen)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nouveau salon"</string>
<string name="screen_create_room_add_people_title">"Inviter des amis"</string>
<string name="screen_create_room_error_creating_room">"Une erreur sest produite lors de la création du salon"</string>
<string name="screen_create_room_private_option_description">"Seules les personnes invitées peuvent accéder à ce salon. Tous les messages sont chiffrés de bout en bout."</string>
<string name="screen_create_room_private_option_title">"Salon privé"</string>
<string name="screen_create_room_public_option_description">"Nimporte qui peut trouver ce salon.
Vous pouvez modifier cela à tout moment dans les paramètres du salon."</string>
<string name="screen_create_room_public_option_title">"Salon public"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Tout le monde peut rejoindre ce salon"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Tout le monde"</string>
<string name="screen_create_room_room_access_section_header">"Accès au salon"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Tout le monde peut demander à rejoindre le salon, mais un administrateur ou un modérateur devra accepter la demande"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Demander à rejoindre"</string>
<string name="screen_create_room_room_address_section_footer">"Pour que ce salon soit visible dans le répertoire des salons publics, vous aurez besoin dune adresse de salon."</string>
<string name="screen_create_room_room_address_section_title">"Adresse du salon"</string>
<string name="screen_create_room_room_name_label">"Nom du salon"</string>
<string name="screen_create_room_room_visibility_section_title">"Visibilité du salon"</string>
<string name="screen_create_room_title">"Créer un salon"</string>
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Új szoba"</string>
<string name="screen_create_room_add_people_title">"Ismerősök meghívása"</string>
<string name="screen_create_room_error_creating_room">"Hiba történt a szoba létrehozásakor"</string>
<string name="screen_create_room_private_option_description">"Csak a meghívottak léphetnek be ebbe a szobába. Az összes üzenet végpontok közti titkosítással van védve."</string>
<string name="screen_create_room_private_option_title">"Privát szoba"</string>
<string name="screen_create_room_public_option_description">"Bárki megtalálhatja ezt a szobát.
Ezt bármikor módosíthatja a szobabeállításokban."</string>
<string name="screen_create_room_public_option_title">"Nyilvános szoba"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Bárki csatlakozhat ehhez a szobához"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Bárki"</string>
<string name="screen_create_room_room_access_section_header">"Szobahozzáférés"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Bárki kérheti, hogy csatlakozzon a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Csatlakozás kérése"</string>
<string name="screen_create_room_room_address_section_footer">"Ahhoz, hogy ez a szoba látható legyen a nyilvános szobák címtárában, meg kell adnia a szoba címét."</string>
<string name="screen_create_room_room_address_section_title">"Szoba címe"</string>
<string name="screen_create_room_room_name_label">"Szoba neve"</string>
<string name="screen_create_room_room_visibility_section_title">"Szoba láthatósága"</string>
<string name="screen_create_room_title">"Szoba létrehozása"</string>
<string name="screen_create_room_topic_label">"Téma (nem kötelező)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Ruangan baru"</string>
<string name="screen_create_room_add_people_title">"Undang orang-orang"</string>
<string name="screen_create_room_error_creating_room">"Terjadi kesalahan saat membuat ruangan"</string>
<string name="screen_create_room_private_option_description">"Hanya orang-orang yang diundang dapat mengakses ruangan ini. Semua pesan terenkripsi secara ujung ke ujung."</string>
<string name="screen_create_room_private_option_title">"Ruangan pribadi"</string>
<string name="screen_create_room_public_option_description">"Siapa pun dapat mencari ruangan ini.
Anda dapat mengubah ini kapan pun dalam pengaturan ruangan."</string>
<string name="screen_create_room_public_option_title">"Ruangan publik"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Siapa pun dapat bergabung dengan ruangan ini"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Siapa pun"</string>
<string name="screen_create_room_room_access_section_header">"Akses Ruangan"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Siapa pun dapat meminta untuk bergabung dengan ruangan tetapi administrator atau moderator harus menerima permintaan tersebut"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Minta untuk bergabung"</string>
<string name="screen_create_room_room_address_section_footer">"Supaya ruangan ini terlihat di direktori ruangan publik, Anda memerlukan alamat ruangan."</string>
<string name="screen_create_room_room_address_section_title">"Alamat ruangan"</string>
<string name="screen_create_room_room_name_label">"Nama ruangan"</string>
<string name="screen_create_room_room_visibility_section_title">"Keterlihatan ruangan"</string>
<string name="screen_create_room_title">"Buat ruangan"</string>
<string name="screen_create_room_topic_label">"Topik (opsional)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nuova stanza"</string>
<string name="screen_create_room_add_people_title">"Invita persone"</string>
<string name="screen_create_room_error_creating_room">"Si è verificato un errore durante la creazione della stanza"</string>
<string name="screen_create_room_private_option_description">"Solo le persone invitate possono accedere a questa stanza. Tutti i messaggi sono cifrati end-to-end."</string>
<string name="screen_create_room_private_option_title">"Stanza privata"</string>
<string name="screen_create_room_public_option_description">"Chiunque può trovare questa stanza.
Puoi modificarlo in qualsiasi momento nelle impostazioni della stanza."</string>
<string name="screen_create_room_public_option_title">"Stanza pubblica"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Chiunque può entrare in questa stanza"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Chiunque"</string>
<string name="screen_create_room_room_access_section_header">"Accesso alla stanza"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Chiunque può chiedere di entrare nella stanza, ma un amministratore o un moderatore dovrà accettare la richiesta"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Chiedi di entrare"</string>
<string name="screen_create_room_room_address_section_footer">"Affinché questa stanza sia visibile nell\'elenco delle stanze pubbliche, è necessario un indirizzo della stanza."</string>
<string name="screen_create_room_room_address_section_title">"Indirizzo della stanza"</string>
<string name="screen_create_room_room_name_label">"Nome stanza"</string>
<string name="screen_create_room_room_visibility_section_title">"Visibilità della stanza"</string>
<string name="screen_create_room_title">"Crea una stanza"</string>
<string name="screen_create_room_topic_label">"Argomento (facoltativo)"</string>
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"ახალი ოთახი"</string>
<string name="screen_create_room_add_people_title">"ხალხის მოწვევა"</string>
<string name="screen_create_room_error_creating_room">"ოთახის შექმნისას შეცდომა მოხდა"</string>
<string name="screen_create_room_private_option_description">"ამ ოთახში შეტყობინებები დაშიფრულია. შემდგომ დაშიფვრის გამორთვა შეუძლებელია."</string>
<string name="screen_create_room_private_option_title">"კერძო ოთახი"</string>
<string name="screen_create_room_public_option_description">"ყველას ამ ოთახის მოძებნა შეუძლია.
თქვენ ნებისმიერ დროს შეგიძლიათ ამის შეცვლა ოთახის პარამეტრებში."</string>
<string name="screen_create_room_room_name_label">"ოთახის სახელი"</string>
<string name="screen_create_room_title">"ოთახის შექმნა"</string>
<string name="screen_create_room_topic_label">"თემა (სურვილისამებრ)"</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"새 방"</string>
<string name="screen_create_room_add_people_title">"사람 초대하기"</string>
<string name="screen_create_room_error_creating_room">"방을 생성하던 중 오류가 발생했어요"</string>
<string name="screen_create_room_private_option_description">"초대받은 사람만 이 방에 액세스할 수 있습니다. 모든 메시지는 종단 간 암호화됩니다."</string>
<string name="screen_create_room_private_option_title">"비공개 방"</string>
<string name="screen_create_room_public_option_description">"누구나 이 방을 찾을 수 있습니다.
방 설정에서 언제든지 변경할 수 있습니다."</string>
<string name="screen_create_room_public_option_title">"공개 방"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"누구나 이 방에 참여할 수 있습니다."</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"누구나"</string>
<string name="screen_create_room_room_access_section_header">"방 액세스"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"누구나 방에 참여 요청을 할 수 있지만, 관리자나 운영자가 요청을 수락해야 합니다."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"참가 요청"</string>
<string name="screen_create_room_room_address_section_footer">"이 방이 공개 방 디렉토리에 표시되려면 방 주소가 필요합니다."</string>
<string name="screen_create_room_room_name_label">"방 이름"</string>
<string name="screen_create_room_room_visibility_section_title">"방 표시 여부"</string>
<string name="screen_create_room_title">"방 만들기"</string>
<string name="screen_create_room_topic_label">"주제 (선택)"</string>
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Naujas kambarys"</string>
<string name="screen_create_room_add_people_title">"Pakviesti žmonių"</string>
<string name="screen_create_room_error_creating_room">"Kuriant kambarį įvyko klaida"</string>
<string name="screen_create_room_private_option_description">"Į šį kambarį gali patekti tik pakviesti žmonės. Visi pranešimai yra užšifruoti nuo pradžios iki galo."</string>
<string name="screen_create_room_private_option_title">"Privatus kambarys"</string>
<string name="screen_create_room_public_option_description">"Bet kas gali rasti šį kambarį.
Tai galite bet kada pakeisti kambario nustatymuose."</string>
<string name="screen_create_room_room_name_label">"Kambario pavadinimas"</string>
<string name="screen_create_room_title">"Kurti kambarį"</string>
<string name="screen_create_room_topic_label">"Tema (nebūtina)"</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nytt rom"</string>
<string name="screen_create_room_add_people_title">"Inviter folk"</string>
<string name="screen_create_room_error_creating_room">"Det oppsto en feil under opprettelsen av rommet"</string>
<string name="screen_create_room_private_option_description">"Bare inviterte personer har tilgang til dette rommet. Alle meldinger er ende-til-ende-kryptert."</string>
<string name="screen_create_room_private_option_title">"Privat rom"</string>
<string name="screen_create_room_public_option_description">"Alle kan finne dette rommet.
Du kan endre dette når som helst i rominnstillingene."</string>
<string name="screen_create_room_public_option_title">"Offentlig rom"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Alle kan bli med i dette rommet"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Alle"</string>
<string name="screen_create_room_room_access_section_header">"Tilgang til rom"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan be om å få bli med i rommet, men en administrator eller moderator må godta forespørselen"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om å bli med"</string>
<string name="screen_create_room_room_address_section_footer">"For at dette rommet skal være synlig i den offentlige romkatalogen, trenger du en romadresse."</string>
<string name="screen_create_room_room_name_label">"Romnavn"</string>
<string name="screen_create_room_room_visibility_section_title">"Romsynlighet"</string>
<string name="screen_create_room_title">"Opprett et rom"</string>
<string name="screen_create_room_topic_label">"Emne (valgfritt)"</string>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nieuwe kamer"</string>
<string name="screen_create_room_add_people_title">"Mensen uitnodigen"</string>
<string name="screen_create_room_error_creating_room">"Er is een fout opgetreden bij het aanmaken van de kamer"</string>
<string name="screen_create_room_private_option_description">"Alleen uitgenodigde personen hebben toegang tot deze kamer. Alle berichten zijn end-to-end versleuteld."</string>
<string name="screen_create_room_private_option_title">"Privé kamer"</string>
<string name="screen_create_room_public_option_description">"Iedereen kan deze kamer vinden.
Je kunt dit op elk gewenst moment wijzigen in de kamerinstellingen."</string>
<string name="screen_create_room_public_option_title">"Openbare kamer"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Iedereen kan toetreden tot deze kamer"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Iedereen"</string>
<string name="screen_create_room_room_access_section_header">"Toegang tot de kamer"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Iedereen kan vragen om toe te treden tot de kamer, maar een beheerder of moderator moet het verzoek accepteren"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Vraag om toe te treden"</string>
<string name="screen_create_room_room_name_label">"Naam van de kamer"</string>
<string name="screen_create_room_title">"Creëer een kamer"</string>
<string name="screen_create_room_topic_label">"Onderwerp (optioneel)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nowy pokój"</string>
<string name="screen_create_room_add_people_title">"Zaproś znajomych"</string>
<string name="screen_create_room_error_creating_room">"Wystąpił błąd w trakcie tworzenia pokoju"</string>
<string name="screen_create_room_private_option_description">"Tylko zaproszone osoby mogą dołączyć do tego pokoju. Wszystkie wiadomości są szyfrowane end-to-end."</string>
<string name="screen_create_room_private_option_title">"Pokój prywatny"</string>
<string name="screen_create_room_public_option_description">"Każdy może znaleźć ten pokój.
Możesz to zmienić w ustawieniach pokoju."</string>
<string name="screen_create_room_public_option_title">"Pokój publiczny"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Każdy może dołączyć do tego pokoju"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Wszyscy"</string>
<string name="screen_create_room_room_access_section_header">"Dostęp do pokoju"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Każdy może poprosić o dołączenie do pokoju, ale administrator lub moderator będzie musiał zatwierdzić prośbę"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Poproś o dołączenie"</string>
<string name="screen_create_room_room_address_section_footer">"Aby ten pokój był widoczny w katalogu pomieszczeń publicznych, będziesz potrzebował adres pokoju."</string>
<string name="screen_create_room_room_address_section_title">"Adres pokoju"</string>
<string name="screen_create_room_room_name_label">"Nazwa pokoju"</string>
<string name="screen_create_room_room_visibility_section_title">"Widoczność pomieszczenia"</string>
<string name="screen_create_room_title">"Utwórz pokój"</string>
<string name="screen_create_room_topic_label">"Temat (opcjonalnie)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nova sala"</string>
<string name="screen_create_room_add_people_title">"Convidar pessoas"</string>
<string name="screen_create_room_error_creating_room">"Ocorreu um erro ao criar a sala"</string>
<string name="screen_create_room_private_option_description">"Apenas as pessoas convidadas podem entrar nesta sala. Todas as mensagens são criptografadas de ponta a ponta."</string>
<string name="screen_create_room_private_option_title">"Sala privada"</string>
<string name="screen_create_room_public_option_description">"Qualquer um pode encontrar esta sala.
Você pode mudar isso a qualquer momento nas configurações da sala."</string>
<string name="screen_create_room_public_option_title">"Sala pública"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Qualquer pessoa pode entrar nesta sala"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Qualquer pessoa"</string>
<string name="screen_create_room_room_access_section_header">"Acesso à sala"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou moderador terá de aceitar a solicitação"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Pedir para entrar"</string>
<string name="screen_create_room_room_address_section_footer">"Para que esta sala fique visível no diretório público de salas, você precisará de um endereço de sala."</string>
<string name="screen_create_room_room_address_section_title">"Endereço da sala"</string>
<string name="screen_create_room_room_name_label">"Nome da sala"</string>
<string name="screen_create_room_room_visibility_section_title">"Visibilidade da sala"</string>
<string name="screen_create_room_title">"Criar uma sala"</string>
<string name="screen_create_room_topic_label">"Tópico (opcional)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nova sala"</string>
<string name="screen_create_room_add_people_title">"Convidar pessoas"</string>
<string name="screen_create_room_error_creating_room">"Ocorreu um erro ao criar a sala"</string>
<string name="screen_create_room_private_option_description">"Apenas as pessoas convidadas podem aceder a esta sala. Todas as mensagens são cifradas ponta-a-ponta."</string>
<string name="screen_create_room_private_option_title">"Sala privada"</string>
<string name="screen_create_room_public_option_description">"Qualquer um pode encontrar esta sala.
Pode alterar esta opção nas definições da sala."</string>
<string name="screen_create_room_public_option_title">"Sala pública"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Qualquer pessoa pode entrar nesta sala"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Qualquer pessoa"</string>
<string name="screen_create_room_room_access_section_header">"Acesso à sala"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou um moderador terá de aceitar o pedido"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Pedir para participar"</string>
<string name="screen_create_room_room_address_section_footer">"Para que esta sala seja visível no diretório público de salas, precisas de um endereço de sala."</string>
<string name="screen_create_room_room_address_section_title">"Endereço da sala"</string>
<string name="screen_create_room_room_name_label">"Nome da sala"</string>
<string name="screen_create_room_room_visibility_section_title">"Visibilidade da sala"</string>
<string name="screen_create_room_title">"Criar uma sala"</string>
<string name="screen_create_room_topic_label">"Descrição (opcional)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Cameră nouă"</string>
<string name="screen_create_room_add_people_title">"Invitați prieteni"</string>
<string name="screen_create_room_error_creating_room">"A apărut o eroare la crearea camerei"</string>
<string name="screen_create_room_private_option_description">"Doar persoanele invitate pot accesa această cameră. Toate mesajele sunt criptate end-to-end."</string>
<string name="screen_create_room_private_option_title">"Cameră privată"</string>
<string name="screen_create_room_public_option_description">"Oricine poate găsi această cameră.
Puteți modifica acest lucru oricând în setări."</string>
<string name="screen_create_room_public_option_title">"Cameră publică"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Oricine se poate alătura acestei camere"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Oricine"</string>
<string name="screen_create_room_room_access_section_header">"Acces la cameră"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Oricine poate cere să se alăture camerei, dar un administrator sau un moderator va trebui să accepte cererea"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Cereți să vă alăturați"</string>
<string name="screen_create_room_room_address_section_footer">"Pentru ca această cameră să fie vizibilă în directorul de camere publice, veți avea nevoie de o adresă de cameră."</string>
<string name="screen_create_room_room_address_section_title">"Adresa camerei"</string>
<string name="screen_create_room_room_name_label">"Numele camerei"</string>
<string name="screen_create_room_room_visibility_section_title">"Vizibilitatea camerei"</string>
<string name="screen_create_room_title">"Creați o cameră"</string>
<string name="screen_create_room_topic_label">"Subiect (opțional)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Создать новую комнату"</string>
<string name="screen_create_room_add_people_title">"Пригласить в комнату"</string>
<string name="screen_create_room_error_creating_room">"Произошла ошибка при создании комнаты"</string>
<string name="screen_create_room_private_option_description">"Доступ в эту комнату имеют только приглашенные пользователи. Все сообщения защищены сквозным шифрованием."</string>
<string name="screen_create_room_private_option_title">"Частная комната"</string>
<string name="screen_create_room_public_option_description">"Любой желающий может найти эту комнату.
Вы можете изменить это в любое время в настройках комнаты."</string>
<string name="screen_create_room_public_option_title">"Общедоступная комната"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Любой желающий может присоединиться к этой комнате"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Любой"</string>
<string name="screen_create_room_room_access_section_header">"Доступ в комнату"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Любой желающий может подать заявку на присоединение к комнате, но администратор или модератор должен будет принять запрос."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Попросить присоединиться"</string>
<string name="screen_create_room_room_address_section_footer">"Чтобы эта комната была видна в каталоге общедоступных, вам необходим ее адрес"</string>
<string name="screen_create_room_room_address_section_title">"Адрес комнаты"</string>
<string name="screen_create_room_room_name_label">"Название комнаты"</string>
<string name="screen_create_room_room_visibility_section_title">"Видимость комнаты"</string>
<string name="screen_create_room_title">"Создать комнату"</string>
<string name="screen_create_room_topic_label">"Тема (необязательно)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nová miestnosť"</string>
<string name="screen_create_room_add_people_title">"Pozvať ľudí"</string>
<string name="screen_create_room_error_creating_room">"Pri vytváraní miestnosti došlo k chybe"</string>
<string name="screen_create_room_private_option_description">"Do tejto miestnosti majú prístup iba pozvaní ľudia. Všetky správy sú end-to-end šifrované."</string>
<string name="screen_create_room_private_option_title">"Súkromná miestnosť"</string>
<string name="screen_create_room_public_option_description">"Túto miestnosť môže nájsť ktokoľvek.
Môžete to kedykoľvek zmeniť v nastaveniach miestnosti."</string>
<string name="screen_create_room_public_option_title">"Verejná miestnosť"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Do tejto miestnosti sa môže pripojiť ktokoľvek"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_create_room_room_access_section_header">"Prístup do miestnosti"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Ktokoľvek môže požiadať o pripojenie sa k miestnosti, ale administrátor alebo moderátor bude musieť žiadosť schváliť"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Požiadať o pripojenie"</string>
<string name="screen_create_room_room_address_section_footer">"Aby bola táto miestnosť viditeľná v adresári verejných miestností, budete potrebovať adresu miestnosti."</string>
<string name="screen_create_room_room_address_section_title">"Adresa miestnosti"</string>
<string name="screen_create_room_room_name_label">"Názov miestnosti"</string>
<string name="screen_create_room_room_visibility_section_title">"Viditeľnosť miestnosti"</string>
<string name="screen_create_room_title">"Vytvoriť miestnosť"</string>
<string name="screen_create_room_topic_label">"Téma (voliteľné)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nytt rum"</string>
<string name="screen_create_room_add_people_title">"Bjud in personer"</string>
<string name="screen_create_room_error_creating_room">"Ett fel uppstod när rummet skapades"</string>
<string name="screen_create_room_private_option_description">"Endast inbjudna personer har tillgång till detta rum. Alla meddelanden är totalsträckskrypterade."</string>
<string name="screen_create_room_private_option_title">"Privat rum"</string>
<string name="screen_create_room_public_option_description">"Vem som helst kan hitta det här rummet.
Du kan ändra detta när som helst i rumsinställningarna."</string>
<string name="screen_create_room_public_option_title">"Offentligt rum"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Vem som helst kan gå med i det här rummet"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Vem som helst"</string>
<string name="screen_create_room_room_access_section_header">"Rumsåtkomst"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Vem som helst kan be om att gå med i rummet men en administratör eller en moderator måste acceptera begäran"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om att gå med"</string>
<string name="screen_create_room_room_address_section_footer">"För att detta rum ska vara synligt i den allmänna rumskatalogen behöver du en rumsadress."</string>
<string name="screen_create_room_room_address_section_title">"Rumsadress"</string>
<string name="screen_create_room_room_name_label">"Rumsnamn"</string>
<string name="screen_create_room_room_visibility_section_title">"Rumssynlighet"</string>
<string name="screen_create_room_title">"Skapa ett rum"</string>
<string name="screen_create_room_topic_label">"Ämne (valfritt)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Yeni oda"</string>
<string name="screen_create_room_add_people_title">"İnsanları davet et"</string>
<string name="screen_create_room_error_creating_room">"Oda oluşturulurken bir hata oluştu"</string>
<string name="screen_create_room_private_option_description">"Bu odaya yalnızca davet edilen kişiler erişebilir. Tüm mesajlar uçtan uca şifrelenir."</string>
<string name="screen_create_room_private_option_title">"Özel oda"</string>
<string name="screen_create_room_public_option_description">"Bu odayı herkes bulabilir.
Bunu istediğiniz zaman oda ayarlarından değiştirebilirsiniz."</string>
<string name="screen_create_room_public_option_title">"Herkese açık oda"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Bu odaya herkes katılabilir"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Herkes"</string>
<string name="screen_create_room_room_access_section_header">"Oda Erişimi"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Herkes odaya katılmayı isteyebilir ancak bir yönetici veya moderatörün isteği kabul etmesi gerekecektir"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Katılmak için sor"</string>
<string name="screen_create_room_room_address_section_footer">"Bu odanın genel oda dizininde görünür olması için bir oda adresine ihtiyacınız olacaktır."</string>
<string name="screen_create_room_room_address_section_title">"Oda adresi"</string>
<string name="screen_create_room_room_name_label">"Oda adı"</string>
<string name="screen_create_room_room_visibility_section_title">"Oda görünürlüğü"</string>
<string name="screen_create_room_title">"Bir oda oluştur"</string>
<string name="screen_create_room_topic_label">"Konu (isteğe bağlı)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Нова кімната"</string>
<string name="screen_create_room_add_people_title">"Запросити людей"</string>
<string name="screen_create_room_error_creating_room">"Під час створення кімнати сталася помилка"</string>
<string name="screen_create_room_private_option_description">"Лише запрошені люди мають доступ до цієї кімнати. Усі повідомлення захищені наскрізним шифруванням."</string>
<string name="screen_create_room_private_option_title">"Приватна кімната (тільки за запрошенням)"</string>
<string name="screen_create_room_public_option_description">"Будь-хто може знайти цю кімнату.
Ви можете змінити це в будь-який час у налаштуваннях кімнати."</string>
<string name="screen_create_room_public_option_title">"Загальнодоступна кімната"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Будь-хто може приєднатися до цієї кімнати"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Кожний"</string>
<string name="screen_create_room_room_access_section_header">"Доступ до кімнати"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Будь-хто може попросити приєднатися до кімнати, але адміністратор або модератор повинен буде прийняти запит"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Запросити приєднатися"</string>
<string name="screen_create_room_room_address_section_footer">"Щоб цю кімнату було видно в каталозі загальнодоступних кімнат, вам знадобиться її адреса."</string>
<string name="screen_create_room_room_address_section_title">"Адреса кімнати"</string>
<string name="screen_create_room_room_name_label">"Назва кімнати"</string>
<string name="screen_create_room_room_visibility_section_title">"Видимість кімнати"</string>
<string name="screen_create_room_title">"Створити кімнату"</string>
<string name="screen_create_room_topic_label">"Тема (необов\'язково)"</string>
</resources>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"نیا کمرہ"</string>
<string name="screen_create_room_add_people_title">"لوگوں کو مدعو کریں"</string>
<string name="screen_create_room_error_creating_room">"کمرہ تخلیق کرتے ہوئے ایک نقص واقع ہوا"</string>
<string name="screen_create_room_private_option_description">"صرف مدعو لوگ ہی اس کمرے تک رسائی حاصل کر سکتے ہیں۔ تمام پیغامات آخر تا آخر مرموز کردہ ہیں۔"</string>
<string name="screen_create_room_private_option_title">"نجی کمرہ"</string>
<string name="screen_create_room_public_option_description">"کوئی بھی یہ کمرہ ڈھونڈ سکتا ہے۔
آپ اسے کمرے کی ترتیبات میں کسی بھی وقت تبدیل کرسکتے ہیں۔"</string>
<string name="screen_create_room_public_option_title">"عوامی کمرہ"</string>
<string name="screen_create_room_room_name_label">"کمرے کا نام"</string>
<string name="screen_create_room_title">"ایک کمرہ بنائیں"</string>
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
</resources>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Yangi xona"</string>
<string name="screen_create_room_add_people_title">"Odamlarni taklif qiling"</string>
<string name="screen_create_room_error_creating_room">"Xonani yaratishda xatolik yuz berdi"</string>
<string name="screen_create_room_private_option_description">"Faqat taklif etilgan shaxslargina bu xonaga kira oladi. Barcha xabarlar boshdan-oxirigacha shifrlanadi."</string>
<string name="screen_create_room_private_option_title">"Shaxsiy xona"</string>
<string name="screen_create_room_public_option_description">"Bu xonani har kim topishi mumkin.
Buni xona sozlamalaridan istalgan vaqtda oʻzgartirishingiz mumkin."</string>
<string name="screen_create_room_public_option_title">"Jamoat xonasi"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Bu xonaga istalgan kishi qoshilishi mumkin"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Har kim"</string>
<string name="screen_create_room_room_access_section_header">"Xonaga kirish"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Xonaga qoshilishni istalgan kishi sorashi mumkin, lekin administrator yoki moderator sorovni qabul qilishi kerak"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Qoshilishni sorang"</string>
<string name="screen_create_room_room_address_section_footer">"Ushbu xona ommaviy xonalar royxatida korinishi uchun sizga xona manzili kerak boladi."</string>
<string name="screen_create_room_room_name_label">"Xona nomi"</string>
<string name="screen_create_room_room_visibility_section_title">"Xonaning korinishi"</string>
<string name="screen_create_room_title">"Xonani yaratish"</string>
<string name="screen_create_room_topic_label">"Mavzu (ixtiyoriy)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"建立聊天室"</string>
<string name="screen_create_room_add_people_title">"邀請夥伴"</string>
<string name="screen_create_room_error_creating_room">"建立聊天室時發生錯誤"</string>
<string name="screen_create_room_private_option_description">"僅被邀請的人才能存取此聊天室。所有訊息均會端到端加密。"</string>
<string name="screen_create_room_private_option_title">"私密聊天室"</string>
<string name="screen_create_room_public_option_description">"任何人都可以找到此聊天室。
您隨時都可以在聊天室設定中變更此設定。"</string>
<string name="screen_create_room_public_option_title">"公開的聊天室"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"任何人都可以加入此聊天室"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"任何人"</string>
<string name="screen_create_room_room_access_section_header">"聊天室存取權"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"任何人都可以要求加入聊天室,但管理員或版主必須接受該請求"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"要求加入"</string>
<string name="screen_create_room_room_address_section_footer">"為了讓此聊天室在公開聊天室目錄中可見,您需要聊天室地址。"</string>
<string name="screen_create_room_room_address_section_title">"聊天室地址"</string>
<string name="screen_create_room_room_name_label">"聊天室名稱"</string>
<string name="screen_create_room_room_visibility_section_title">"聊天室能見度"</string>
<string name="screen_create_room_title">"建立聊天室"</string>
<string name="screen_create_room_topic_label">"主題(非必填)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"新聊天室"</string>
<string name="screen_create_room_add_people_title">"邀请朋友"</string>
<string name="screen_create_room_error_creating_room">"创建聊天室时出错"</string>
<string name="screen_create_room_private_option_description">"只有受邀用户才能访问此聊天室。所有消息均经过端到端加密。"</string>
<string name="screen_create_room_private_option_title">"私有聊天室"</string>
<string name="screen_create_room_public_option_description">"任何人都能找到此聊天室。
你可以随时在聊天室设置中更改。"</string>
<string name="screen_create_room_public_option_title">"公共聊天室"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"任何人都可以加入此房间"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"任何人"</string>
<string name="screen_create_room_room_access_section_header">"房间访问权限"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"任何人都可以请求加入房间,但必须由管理员或审核人接受"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"请求加入"</string>
<string name="screen_create_room_room_address_section_footer">"要使该房间在公开房间目录中可见,您需要一个房间地址。"</string>
<string name="screen_create_room_room_address_section_title">"房间地址"</string>
<string name="screen_create_room_room_name_label">"聊天室名称"</string>
<string name="screen_create_room_room_visibility_section_title">"房间可见性"</string>
<string name="screen_create_room_title">"创建聊天室"</string>
<string name="screen_create_room_topic_label">"主题(可选)"</string>
</resources>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"New room"</string>
<string name="screen_create_room_add_people_title">"Invite people"</string>
<string name="screen_create_room_error_creating_room">"An error occurred when creating the room"</string>
<string name="screen_create_room_private_option_description">"Only people invited can access this room. All messages are end-to-end encrypted."</string>
<string name="screen_create_room_private_option_title">"Private room"</string>
<string name="screen_create_room_public_option_description">"Anyone can find this room.
You can change this anytime in room settings."</string>
<string name="screen_create_room_public_option_title">"Public room"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Anyone can join this room"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Anyone"</string>
<string name="screen_create_room_room_access_section_header">"Room Access"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Anyone can ask to join the room but an administrator or a moderator will have to accept the request"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Ask to join"</string>
<string name="screen_create_room_room_address_section_footer">"In order for this room to be visible in the public room directory, you will need a room address."</string>
<string name="screen_create_room_room_address_section_title">"Room address"</string>
<string name="screen_create_room_room_name_label">"Room name"</string>
<string name="screen_create_room_room_visibility_section_title">"Room visibility"</string>
<string name="screen_create_room_title">"Create a room"</string>
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
</resources>

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.createroom.impl
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
import com.google.common.truth.Truth.assertThat
import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.node.TestParentNode
import org.junit.Rule
import org.junit.Test
class DefaultCreateRoomEntryPointTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun `test node builder`() {
val entryPoint = DefaultCreateRoomEntryPoint()
val parentNode = TestParentNode.create { buildContext, plugins ->
CreateRoomFlowNode(
buildContext = buildContext,
plugins = plugins,
)
}
val callback = object : CreateRoomEntryPoint.Callback {
override fun onRoomCreated(roomId: RoomId) = lambdaError()
}
val result = entryPoint.createNode(
parentNode = parentNode,
buildContext = BuildContext.root(null),
callback = callback,
)
assertThat(result.plugins).contains(callback)
}
}

View File

@@ -0,0 +1,416 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.startchat.impl.configureroom
import android.net.Uri
import androidx.core.net.toUri
import app.cash.turbine.TurbineTestContext
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.CreatedRoom
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomEvents
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomPresenter
import io.element.android.features.createroom.impl.configureroom.ConfigureRoomState
import io.element.android.features.createroom.impl.configureroom.CreateRoomConfig
import io.element.android.features.createroom.impl.configureroom.CreateRoomConfigStore
import io.element.android.features.createroom.impl.configureroom.RoomAccess
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.libraries.architecture.AsyncAction
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper
import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediapickers.test.FakePickerProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.io.File
import java.util.Optional
private const val AN_URI_FROM_CAMERA = "content://uri_from_camera"
private const val AN_URI_FROM_CAMERA_2 = "content://uri_from_camera_2"
private const val AN_URI_FROM_GALLERY = "content://uri_from_gallery"
@RunWith(RobolectricTestRunner::class)
class ConfigureRoomPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
@Before
fun setup() {
mockkStatic(File::readBytes)
every { any<File>().readBytes() } returns byteArrayOf()
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun `present - initial state`() = runTest {
val presenter = createConfigureRoomPresenter()
presenter.test {
val initialState = initialState()
assertThat(initialState.config).isEqualTo(CreateRoomConfig())
assertThat(initialState.config.roomName).isNull()
assertThat(initialState.config.topic).isNull()
assertThat(initialState.config.invites).isEmpty()
assertThat(initialState.config.avatarUri).isNull()
assertThat(initialState.config.roomVisibility).isEqualTo(RoomVisibilityState.Private)
assertThat(initialState.createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(initialState.homeserverName).isEqualTo("matrix.org")
}
}
@Test
fun `present - create room button is enabled only if the required fields are completed`() = runTest {
val presenter = createConfigureRoomPresenter()
presenter.test {
val initialState = initialState()
var config = initialState.config
assertThat(initialState.isValid).isFalse()
// Room name not empty
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
var newState: ConfigureRoomState = awaitItem()
config = config.copy(roomName = A_ROOM_NAME)
assertThat(newState.config).isEqualTo(config)
assertThat(newState.isValid).isTrue()
// Clear room name
newState.eventSink(ConfigureRoomEvents.RoomNameChanged(""))
newState = awaitItem()
config = config.copy(roomName = null)
assertThat(newState.config).isEqualTo(config)
assertThat(newState.isValid).isFalse()
}
}
@Test
fun `present - state is updated when fields are changed`() = runTest {
val pickerProvider = FakePickerProvider()
val permissionsPresenter = FakePermissionsPresenter()
val roomAliasHelper = FakeRoomAliasHelper()
val presenter = createConfigureRoomPresenter(
dataStore = CreateRoomConfigStore(roomAliasHelper),
pickerProvider = pickerProvider,
permissionsPresenter = permissionsPresenter,
)
presenter.test {
val initialState = initialState()
var expectedConfig = CreateRoomConfig()
assertThat(initialState.config).isEqualTo(expectedConfig)
// Room name
initialState.eventSink(ConfigureRoomEvents.RoomNameChanged(A_ROOM_NAME))
var newState = awaitItem()
expectedConfig = expectedConfig.copy(roomName = A_ROOM_NAME)
assertThat(newState.config).isEqualTo(expectedConfig)
// Room topic
newState.eventSink(ConfigureRoomEvents.TopicChanged(A_MESSAGE))
newState = awaitItem()
expectedConfig = expectedConfig.copy(topic = A_MESSAGE)
assertThat(newState.config).isEqualTo(expectedConfig)
// Room avatar
// Pick avatar
pickerProvider.givenResult(null)
// From gallery
val uriFromGallery = AN_URI_FROM_GALLERY
pickerProvider.givenResult(uriFromGallery.toUri())
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
newState = awaitItem()
expectedConfig = expectedConfig.copy(avatarUri = uriFromGallery)
assertThat(newState.config).isEqualTo(expectedConfig)
// From camera
val uriFromCamera = AN_URI_FROM_CAMERA
pickerProvider.givenResult(uriFromCamera.toUri())
assertThat(newState.cameraPermissionState.permissionGranted).isFalse()
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto))
newState = awaitItem()
assertThat(newState.cameraPermissionState.showDialog).isTrue()
permissionsPresenter.setPermissionGranted()
newState = awaitItem()
assertThat(newState.cameraPermissionState.permissionGranted).isTrue()
newState = awaitItem()
expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera)
assertThat(newState.config).isEqualTo(expectedConfig)
// Do it again, no permission is requested
val uriFromCamera2 = AN_URI_FROM_CAMERA_2
pickerProvider.givenResult(uriFromCamera2.toUri())
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto))
newState = awaitItem()
expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera2)
assertThat(newState.config).isEqualTo(expectedConfig)
// Remove
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.Remove))
newState = awaitItem()
expectedConfig = expectedConfig.copy(avatarUri = null)
assertThat(newState.config).isEqualTo(expectedConfig)
// Room privacy
newState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
newState = awaitItem()
expectedConfig = expectedConfig.copy(
roomVisibility = RoomVisibilityState.Public(
roomAddress = RoomAddress.AutoFilled(roomAliasHelper.roomAliasNameFromRoomDisplayName(expectedConfig.roomName ?: "")),
roomAccess = RoomAccess.Anyone,
)
)
assertThat(newState.config).isEqualTo(expectedConfig)
}
}
@Test
fun `present - trigger create room action`() = runTest {
val matrixClient = createMatrixClient()
val presenter = createConfigureRoomPresenter(
matrixClient = matrixClient
)
presenter.test {
val initialState = initialState()
val createRoomResult = Result.success(RoomId("!createRoomResult:domain"))
matrixClient.givenCreateRoomResult(createRoomResult)
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
val stateAfterCreateRoom = awaitItem()
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Success::class.java)
assertThat(stateAfterCreateRoom.createRoomAction.dataOrNull()).isEqualTo(createRoomResult.getOrNull())
}
}
@Test
fun `present - record analytics when creating room`() = runTest {
val matrixClient = createMatrixClient()
val analyticsService = FakeAnalyticsService()
val presenter = createConfigureRoomPresenter(
matrixClient = matrixClient,
analyticsService = analyticsService
)
presenter.test {
val initialState = initialState()
val createRoomResult = Result.success(RoomId("!createRoomResult:domain"))
matrixClient.givenCreateRoomResult(createRoomResult)
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
skipItems(2)
val analyticsEvent = analyticsService.capturedEvents.filterIsInstance<CreatedRoom>().firstOrNull()
assertThat(analyticsEvent).isNotNull()
assertThat(analyticsEvent?.isDM).isFalse()
}
}
@Test
fun `present - trigger create room with upload error and retry`() = runTest {
val matrixClient = createMatrixClient()
val analyticsService = FakeAnalyticsService()
val mediaPreProcessor = FakeMediaPreProcessor()
val dataStore = CreateRoomConfigStore(FakeRoomAliasHelper())
val presenter = createConfigureRoomPresenter(
dataStore = dataStore,
mediaPreProcessor = mediaPreProcessor,
matrixClient = matrixClient,
analyticsService = analyticsService
)
presenter.test {
val initialState = initialState()
dataStore.setAvatarUri(Uri.parse(AN_URI_FROM_GALLERY))
skipItems(1)
mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(mockk(), mockk(), mockk())))
matrixClient.givenUploadMediaResult(Result.failure(AN_EXCEPTION))
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
val stateAfterCreateRoom = awaitItem()
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java)
assertThat(analyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
matrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL))
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Success::class.java)
}
}
@Test
fun `present - trigger retry and cancel actions`() = runTest {
val fakeMatrixClient = createMatrixClient()
val presenter = createConfigureRoomPresenter(
matrixClient = fakeMatrixClient
)
presenter.test {
val initialState = initialState()
val createRoomResult = Result.failure<RoomId>(AN_EXCEPTION)
fakeMatrixClient.givenCreateRoomResult(createRoomResult)
// Create
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
val stateAfterCreateRoom = awaitItem()
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java)
assertThat((stateAfterCreateRoom.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull())
// Retry
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
val stateAfterRetry = awaitItem()
assertThat(stateAfterRetry.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java)
assertThat((stateAfterRetry.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull())
// Cancel
stateAfterRetry.eventSink(ConfigureRoomEvents.CancelCreateRoom)
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - address is invalid when format is invalid`() = runTest {
val aliasHelper = FakeRoomAliasHelper(
isRoomAliasValidLambda = { false }
)
val presenter = createConfigureRoomPresenter(
roomAliasHelper = aliasHelper
)
presenter.test {
val initialState = initialState()
initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
skipItems(1)
initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("invalid address"))
skipItems(1)
advanceUntilIdle()
awaitItem().also { state ->
assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.InvalidSymbols)
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - address is not available when alias is not available`() = runTest {
val fakeMatrixClient = createMatrixClient(isAliasAvailable = false)
val presenter = createConfigureRoomPresenter(
matrixClient = fakeMatrixClient,
)
presenter.test {
val initialState = initialState()
initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
skipItems(1)
initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address"))
skipItems(1)
advanceUntilIdle()
awaitItem().also { state ->
assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.NotAvailable)
}
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - address is valid when alias is available and format is valid`() = runTest {
val fakeMatrixClient = createMatrixClient(isAliasAvailable = true)
val presenter = createConfigureRoomPresenter(
matrixClient = fakeMatrixClient,
)
presenter.test {
val initialState = initialState()
initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
skipItems(1)
initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address"))
skipItems(1)
advanceUntilIdle()
awaitItem().also { state ->
assertThat(state.roomAddressValidity).isEqualTo(RoomAddressValidity.Valid)
}
}
}
private suspend fun TurbineTestContext<ConfigureRoomState>.initialState(): ConfigureRoomState {
skipItems(1)
return awaitItem()
}
private fun createMatrixClient(isAliasAvailable: Boolean = true) = FakeMatrixClient(
userIdServerNameLambda = { "matrix.org" },
resolveRoomAliasResult = {
val resolvedRoomAlias = if (isAliasAvailable) {
Optional.empty()
} else {
Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList()))
}
Result.success(resolvedRoomAlias)
}
)
private fun createConfigureRoomPresenter(
roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(),
dataStore: CreateRoomConfigStore = CreateRoomConfigStore(roomAliasHelper),
matrixClient: MatrixClient = createMatrixClient(),
pickerProvider: PickerProvider = FakePickerProvider(),
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
analyticsService: AnalyticsService = FakeAnalyticsService(),
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
isKnockFeatureEnabled: Boolean = true,
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
) = ConfigureRoomPresenter(
dataStore = dataStore,
matrixClient = matrixClient,
mediaPickerProvider = pickerProvider,
mediaPreProcessor = mediaPreProcessor,
analyticsService = analyticsService,
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
roomAliasHelper = roomAliasHelper,
featureFlagService = FakeFeatureFlagService(
mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled)
),
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
)
}