Merge pull request #3804 from element-hq/feature/fga/update_create_room_flow
Knocking : update create room flow
This commit is contained in:
commit
a678fe44a0
57 changed files with 743 additions and 418 deletions
|
|
@ -40,6 +40,7 @@ dependencies {
|
|||
implementation(projects.libraries.usersearch.impl)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
api(projects.features.createroom.api)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
|
|
@ -56,6 +57,7 @@ dependencies {
|
|||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.usersearch.test)
|
||||
testImplementation(projects.features.createroom.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
package io.element.android.features.createroom.impl
|
||||
|
||||
import android.net.Uri
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomPrivacy
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -18,5 +18,7 @@ data class CreateRoomConfig(
|
|||
val topic: String? = null,
|
||||
val avatarUri: Uri? = null,
|
||||
val invites: ImmutableList<MatrixUser> = persistentListOf(),
|
||||
val privacy: RoomPrivacy = RoomPrivacy.Private,
|
||||
)
|
||||
val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private,
|
||||
) {
|
||||
val isValid = roomName.isNullOrEmpty().not() && roomVisibility.isValid()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,12 @@
|
|||
package io.element.android.features.createroom.impl
|
||||
|
||||
import android.net.Uri
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomPrivacy
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAccess
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAccessItem
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAddress
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomAddressErrorState
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState
|
||||
import io.element.android.features.createroom.impl.di.CreateRoomScope
|
||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
|
|
@ -17,6 +22,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.getAndUpdate
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -31,28 +37,89 @@ class CreateRoomDataStore @Inject constructor(
|
|||
field = value
|
||||
}
|
||||
|
||||
fun getCreateRoomConfig(): Flow<CreateRoomConfig> = combine(
|
||||
val createRoomConfigWithInvites: Flow<CreateRoomConfig> = combine(
|
||||
selectedUserListDataStore.selectedUsers(),
|
||||
createRoomConfigFlow,
|
||||
) { selectedUsers, config ->
|
||||
config.copy(invites = selectedUsers.toImmutableList())
|
||||
}
|
||||
|
||||
fun setRoomName(roomName: String?) {
|
||||
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(roomName = roomName?.takeIf { it.isNotEmpty() }))
|
||||
fun setRoomName(roomName: String) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
/*
|
||||
val newVisibility = when (config.roomVisibility) {
|
||||
is RoomVisibilityState.Public -> {
|
||||
val roomAddress = config.roomVisibility.roomAddress
|
||||
if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) {
|
||||
config.roomVisibility.copy(
|
||||
roomAddress = RoomAddress.AutoFilled(roomName),
|
||||
)
|
||||
} else {
|
||||
config.roomVisibility
|
||||
}
|
||||
}
|
||||
else -> config.roomVisibility
|
||||
}
|
||||
*/
|
||||
config.copy(
|
||||
roomName = roomName.takeIf { it.isNotEmpty() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTopic(topic: String?) {
|
||||
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(topic = topic?.takeIf { it.isNotEmpty() }))
|
||||
fun setTopic(topic: String) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(topic = topic.takeIf { it.isNotEmpty() })
|
||||
}
|
||||
}
|
||||
|
||||
fun setAvatarUri(uri: Uri?, cached: Boolean = false) {
|
||||
cachedAvatarUri = uri.takeIf { cached }
|
||||
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(avatarUri = uri))
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(avatarUri = uri)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPrivacy(privacy: RoomPrivacy) {
|
||||
createRoomConfigFlow.tryEmit(createRoomConfigFlow.value.copy(privacy = privacy))
|
||||
fun setRoomVisibility(visibility: RoomVisibilityItem) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(
|
||||
roomVisibility = when (visibility) {
|
||||
RoomVisibilityItem.Private -> RoomVisibilityState.Private
|
||||
RoomVisibilityItem.Public -> RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled(config.roomName.orEmpty()),
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
roomAccess = RoomAccess.Anyone,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRoomAddress(address: String) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(
|
||||
roomVisibility = when (config.roomVisibility) {
|
||||
is RoomVisibilityState.Public -> config.roomVisibility.copy(roomAddress = RoomAddress.Edited(address))
|
||||
else -> config.roomVisibility
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRoomAccess(access: RoomAccessItem) {
|
||||
createRoomConfigFlow.getAndUpdate { config ->
|
||||
config.copy(
|
||||
roomVisibility = when (config.roomVisibility) {
|
||||
is RoomVisibilityState.Public -> {
|
||||
when (access) {
|
||||
RoomAccessItem.Anyone -> config.roomVisibility.copy(roomAccess = RoomAccess.Anyone)
|
||||
RoomAccessItem.AskToJoin -> config.roomVisibility.copy(roomAccess = RoomAccess.Knocking)
|
||||
}
|
||||
}
|
||||
else -> config.roomVisibility
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCachedData() {
|
||||
|
|
|
|||
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.createroom.impl.configureroom.RoomPrivacyItem
|
||||
import io.element.android.features.createroom.impl.configureroom.roomPrivacyItems
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.RadioButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun RoomPrivacyOption(
|
||||
roomPrivacyItem: RoomPrivacyItem,
|
||||
onOptionClick: (RoomPrivacyItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isSelected: Boolean = false,
|
||||
) {
|
||||
Row(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = { onOptionClick(roomPrivacyItem) },
|
||||
role = Role.RadioButton,
|
||||
)
|
||||
.padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
resourceId = roomPrivacyItem.icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = roomPrivacyItem.title,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Spacer(Modifier.size(3.dp))
|
||||
Text(
|
||||
text = roomPrivacyItem.description,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
RadioButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.size(48.dp),
|
||||
selected = isSelected,
|
||||
// null recommended for accessibility with screenreaders
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomPrivacyOptionPreview() = ElementPreview {
|
||||
val aRoomPrivacyItem = roomPrivacyItems().first()
|
||||
Column {
|
||||
RoomPrivacyOption(
|
||||
roomPrivacyItem = aRoomPrivacyItem,
|
||||
onOptionClick = {},
|
||||
isSelected = true,
|
||||
)
|
||||
RoomPrivacyOption(
|
||||
roomPrivacyItem = aRoomPrivacyItem,
|
||||
onOptionClick = {},
|
||||
isSelected = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,16 +7,17 @@
|
|||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||
|
||||
sealed interface ConfigureRoomEvents {
|
||||
data class RoomNameChanged(val name: String) : ConfigureRoomEvents
|
||||
data class TopicChanged(val topic: String) : ConfigureRoomEvents
|
||||
data class RoomPrivacyChanged(val privacy: RoomPrivacy) : ConfigureRoomEvents
|
||||
data class RemoveFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
|
||||
data class CreateRoom(val config: CreateRoomConfig) : ConfigureRoomEvents
|
||||
data class RoomVisibilityChanged(val visibilityItem: RoomVisibilityItem) : ConfigureRoomEvents
|
||||
data class RoomAccessChanged(val roomAccess: RoomAccessItem) : ConfigureRoomEvents
|
||||
data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents
|
||||
data class RemoveUserFromSelection(val matrixUser: MatrixUser) : ConfigureRoomEvents
|
||||
data object CreateRoom : ConfigureRoomEvents
|
||||
data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents
|
||||
data object CancelCreateRoom : ConfigureRoomEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ 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
|
||||
|
|
@ -38,6 +40,7 @@ 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 javax.inject.Inject
|
||||
|
||||
class ConfigureRoomPresenter @Inject constructor(
|
||||
|
|
@ -47,6 +50,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
private val mediaPreProcessor: MediaPreProcessor,
|
||||
private val analyticsService: AnalyticsService,
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : Presenter<ConfigureRoomState> {
|
||||
private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA)
|
||||
private var pendingPermissionRequest = false
|
||||
|
|
@ -54,7 +58,9 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
@Composable
|
||||
override fun present(): ConfigureRoomState {
|
||||
val cameraPermissionState = cameraPermissionPresenter.present()
|
||||
val createRoomConfig = dataStore.getCreateRoomConfig().collectAsState(CreateRoomConfig())
|
||||
val createRoomConfig = dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig())
|
||||
val homeserverName = remember { matrixClient.userIdServerName() }
|
||||
val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false)
|
||||
|
||||
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
|
||||
onResult = { uri -> if (uri != null) dataStore.setAvatarUri(uri = uri, cached = true) },
|
||||
|
|
@ -92,9 +98,11 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
when (event) {
|
||||
is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name)
|
||||
is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic)
|
||||
is ConfigureRoomEvents.RoomPrivacyChanged -> dataStore.setPrivacy(event.privacy)
|
||||
is ConfigureRoomEvents.RemoveFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
|
||||
is ConfigureRoomEvents.CreateRoom -> createRoom(event.config)
|
||||
is ConfigureRoomEvents.RoomVisibilityChanged -> dataStore.setRoomVisibility(event.visibilityItem)
|
||||
is ConfigureRoomEvents.RemoveUserFromSelection -> dataStore.selectedUserListDataStore.removeUserFromSelection(event.matrixUser)
|
||||
is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess)
|
||||
is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress)
|
||||
is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig.value)
|
||||
is ConfigureRoomEvents.HandleAvatarAction -> {
|
||||
when (event.action) {
|
||||
AvatarAction.ChoosePhoto -> galleryImagePicker.launch()
|
||||
|
|
@ -113,10 +121,12 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
return ConfigureRoomState(
|
||||
isKnockFeatureEnabled = isKnockFeatureEnabled,
|
||||
config = createRoomConfig.value,
|
||||
avatarActions = avatarActions,
|
||||
createRoomAction = createRoomAction.value,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
homeserverName = homeserverName,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
|
@ -127,21 +137,40 @@ class ConfigureRoomPresenter @Inject constructor(
|
|||
) = launch {
|
||||
suspend {
|
||||
val avatarUrl = config.avatarUri?.let { uploadAvatar(it) }
|
||||
val params = CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
topic = config.topic,
|
||||
isEncrypted = config.privacy == RoomPrivacy.Private,
|
||||
isDirect = false,
|
||||
visibility = if (config.privacy == RoomPrivacy.Public) RoomVisibility.PUBLIC else RoomVisibility.PRIVATE,
|
||||
preset = if (config.privacy == RoomPrivacy.Public) RoomPreset.PUBLIC_CHAT else RoomPreset.PRIVATE_CHAT,
|
||||
invite = config.invites.map { it.userId },
|
||||
avatar = avatarUrl,
|
||||
)
|
||||
matrixClient.createRoom(params).getOrThrow()
|
||||
.also {
|
||||
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,
|
||||
canonicalAlias = config.roomVisibility.roomAddress()
|
||||
)
|
||||
} else {
|
||||
CreateRoomParameters(
|
||||
name = config.roomName,
|
||||
topic = config.topic,
|
||||
isEncrypted = config.roomVisibility is RoomVisibilityState.Private,
|
||||
isDirect = false,
|
||||
visibility = RoomVisibility.PRIVATE,
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ 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 homeserverName: String,
|
||||
val eventSink: (ConfigureRoomEvents) -> Unit
|
||||
) {
|
||||
val isCreateButtonEnabled: Boolean = config.roomName.isNullOrEmpty().not()
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,30 +10,59 @@ package io.element.android.features.createroom.impl.configureroom
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||
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.permissions.api.PermissionsState
|
||||
import io.element.android.libraries.permissions.api.aPermissionsState
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomState> {
|
||||
override val values: Sequence<ConfigureRoomState>
|
||||
get() = sequenceOf(
|
||||
aConfigureRoomState(),
|
||||
aConfigureRoomState().copy(
|
||||
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 shouldn’t be more than 3 lines",
|
||||
invites = aMatrixUserList().toImmutableList(),
|
||||
privacy = RoomPrivacy.Public,
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room 101"),
|
||||
roomAccess = RoomAccess.Knocking,
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
),
|
||||
),
|
||||
),
|
||||
aConfigureRoomState(
|
||||
config = CreateRoomConfig(
|
||||
roomName = "Room 101",
|
||||
topic = "Room topic for this room when the text goes onto multiple lines and is really long, there shouldn’t be more than 3 lines",
|
||||
invites = aMatrixUserList().toImmutableList(),
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled("Room 101"),
|
||||
roomAccess = RoomAccess.Knocking,
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aConfigureRoomState() = ConfigureRoomState(
|
||||
config = CreateRoomConfig(),
|
||||
avatarActions = persistentListOf(),
|
||||
createRoomAction = AsyncAction.Uninitialized,
|
||||
cameraPermissionState = aPermissionsState(showDialog = false),
|
||||
eventSink = { },
|
||||
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",
|
||||
eventSink: (ConfigureRoomEvents) -> Unit = { },
|
||||
) = ConfigureRoomState(
|
||||
config = config,
|
||||
isKnockFeatureEnabled = isKnockFeatureEnabled,
|
||||
avatarActions = avatarActions.toImmutableList(),
|
||||
createRoomAction = createRoomAction,
|
||||
cameraPermissionState = cameraPermissionState,
|
||||
homeserverName = homeserverName,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import android.net.Uri
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -21,6 +23,7 @@ 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.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -33,18 +36,22 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
|
||||
import io.element.android.libraries.designsystem.components.LabelledTextField
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
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
|
||||
|
|
@ -72,11 +79,11 @@ fun ConfigureRoomView(
|
|||
modifier = modifier.clearFocusOnTap(focusManager),
|
||||
topBar = {
|
||||
ConfigureRoomToolbar(
|
||||
isNextActionEnabled = state.isCreateButtonEnabled,
|
||||
isNextActionEnabled = state.config.isValid,
|
||||
onBackClick = onBackClick,
|
||||
onNextClick = {
|
||||
focusManager.clearFocus()
|
||||
state.eventSink(ConfigureRoomEvents.CreateRoom(state.config))
|
||||
state.eventSink(ConfigureRoomEvents.CreateRoom)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -103,23 +110,42 @@ fun ConfigureRoomView(
|
|||
)
|
||||
if (state.config.invites.isNotEmpty()) {
|
||||
SelectedUsersRowList(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp),
|
||||
selectedUsers = state.config.invites,
|
||||
onUserRemove = {
|
||||
focusManager.clearFocus()
|
||||
state.eventSink(ConfigureRoomEvents.RemoveFromSelection(it))
|
||||
state.eventSink(ConfigureRoomEvents.RemoveUserFromSelection(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
RoomPrivacyOptions(
|
||||
modifier = Modifier.padding(bottom = 40.dp),
|
||||
selected = state.config.privacy,
|
||||
RoomVisibilityOptions(
|
||||
selected = when (state.config.roomVisibility) {
|
||||
is RoomVisibilityState.Private -> RoomVisibilityItem.Private
|
||||
is RoomVisibilityState.Public -> RoomVisibilityItem.Public
|
||||
},
|
||||
onOptionClick = {
|
||||
focusManager.clearFocus()
|
||||
state.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(it.privacy))
|
||||
state.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(it))
|
||||
},
|
||||
)
|
||||
if (state.config.roomVisibility is RoomVisibilityState.Public && 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,
|
||||
homeserverName = state.homeserverName,
|
||||
onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +165,7 @@ fun ConfigureRoomView(
|
|||
},
|
||||
onSuccess = { onCreateRoomSuccess(it) },
|
||||
errorMessage = { stringResource(R.string.screen_create_room_error_creating_room) },
|
||||
onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) },
|
||||
onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom) },
|
||||
onErrorDismiss = { state.eventSink(ConfigureRoomEvents.CancelCreateRoom) },
|
||||
)
|
||||
|
||||
|
|
@ -221,23 +247,123 @@ private fun RoomTopic(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomPrivacyOptions(
|
||||
selected: RoomPrivacy?,
|
||||
onOptionClick: (RoomPrivacyItem) -> Unit,
|
||||
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,
|
||||
) {
|
||||
val items = roomPrivacyItems()
|
||||
Column(modifier = modifier.selectableGroup()) {
|
||||
items.forEach { item ->
|
||||
RoomPrivacyOption(
|
||||
roomPrivacyItem = item,
|
||||
isSelected = selected == item.privacy,
|
||||
onOptionClick = onOptionClick,
|
||||
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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RoomAddressField(
|
||||
address: RoomAddress,
|
||||
homeserverName: String,
|
||||
onAddressChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = stringResource(R.string.screen_create_room_room_address_section_title),
|
||||
)
|
||||
|
||||
TextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = address.value,
|
||||
leadingIcon = {
|
||||
Text(
|
||||
text = "#",
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Text(
|
||||
text = homeserverName,
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
modifier = Modifier.padding(end = 16.dp)
|
||||
)
|
||||
},
|
||||
supportingText = {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_create_room_room_address_section_footer),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
)
|
||||
},
|
||||
onValueChange = onAddressChange,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun ConfigureRoomViewPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) = ElementPreview {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import io.element.android.libraries.matrix.api.createroom.JoinRuleOverride
|
||||
|
||||
enum class RoomAccess {
|
||||
Anyone,
|
||||
Knocking
|
||||
}
|
||||
|
||||
fun RoomAccess.toJoinRule(): JoinRuleOverride {
|
||||
return when (this) {
|
||||
RoomAccess.Anyone -> JoinRuleOverride.None
|
||||
RoomAccess.Knocking -> JoinRuleOverride.Knock
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import io.element.android.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,
|
||||
),
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
sealed class RoomAddress(open val value: String) {
|
||||
data class AutoFilled(override val value: String) : RoomAddress(value)
|
||||
data class Edited(override val value: String) : RoomAddress(value)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
/**
|
||||
* Represents the error state of a room address.
|
||||
*/
|
||||
sealed interface RoomAddressErrorState {
|
||||
data object InvalidCharacters : RoomAddressErrorState
|
||||
data object AlreadyExists : RoomAddressErrorState
|
||||
data object None : RoomAddressErrorState
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
enum class RoomPrivacy {
|
||||
Private,
|
||||
Public,
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
data class RoomPrivacyItem(
|
||||
val privacy: RoomPrivacy,
|
||||
@DrawableRes val icon: Int,
|
||||
val title: String,
|
||||
val description: String,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun roomPrivacyItems(): ImmutableList<RoomPrivacyItem> {
|
||||
return RoomPrivacy.entries
|
||||
.map {
|
||||
when (it) {
|
||||
RoomPrivacy.Private -> RoomPrivacyItem(
|
||||
privacy = it,
|
||||
icon = CompoundDrawables.ic_compound_lock_solid,
|
||||
title = stringResource(R.string.screen_create_room_private_option_title),
|
||||
description = stringResource(R.string.screen_create_room_private_option_description),
|
||||
)
|
||||
RoomPrivacy.Public -> RoomPrivacyItem(
|
||||
privacy = it,
|
||||
icon = CompoundDrawables.ic_compound_public,
|
||||
title = stringResource(R.string.screen_create_room_public_option_title),
|
||||
description = stringResource(R.string.screen_create_room_public_option_description),
|
||||
)
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
|
||||
enum class RoomVisibilityItem(
|
||||
@DrawableRes val icon: Int,
|
||||
@StringRes val title: Int,
|
||||
@StringRes val description: Int
|
||||
) {
|
||||
Private(
|
||||
icon = CompoundDrawables.ic_compound_lock,
|
||||
title = R.string.screen_create_room_private_option_title,
|
||||
description = R.string.screen_create_room_private_option_description,
|
||||
),
|
||||
Public(
|
||||
icon = CompoundDrawables.ic_compound_public,
|
||||
title = R.string.screen_create_room_public_option_title,
|
||||
description = R.string.screen_create_room_public_option_description,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import java.util.Optional
|
||||
|
||||
sealed interface RoomVisibilityState {
|
||||
data object Private : RoomVisibilityState
|
||||
|
||||
data class Public(
|
||||
val roomAddress: RoomAddress,
|
||||
val roomAddressErrorState: RoomAddressErrorState,
|
||||
val roomAccess: RoomAccess,
|
||||
) : RoomVisibilityState
|
||||
|
||||
fun roomAddress(): Optional<String> {
|
||||
return when (this) {
|
||||
is Private -> Optional.empty()
|
||||
is Public -> Optional.of(roomAddress.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
return when (this) {
|
||||
is Private -> true
|
||||
is Public -> roomAddressErrorState is RoomAddressErrorState.None && roomAddress.value.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,9 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Při pokusu o zahájení chatu došlo k chybě"</string>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
<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_name_label">"Όνομα δωματίου"</string>
|
||||
<string name="screen_create_room_title">"Δημιούργησε ένα δωμάτιο"</string>
|
||||
<string name="screen_create_room_topic_label">"Θέμα (προαιρετικό)"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Vestluse alustamisel tekkis viga"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<string name="screen_create_room_public_option_description">"N’importe 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 d’une 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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Une erreur s’est produite lors de la tentative de création de la discussion"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Hiba történt a csevegés indításakor"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Terjadi kesalahan saat mencoba memulai obrolan"</string>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Wystąpił błąd podczas próby rozpoczęcia czatu"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Ocorreu um erro ao tentar iniciar uma conversa"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Произошла ошибка при запуске чата"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"Pri pokuse o spustenie konverzácie sa vyskytla chyba"</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
<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>
|
||||
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
||||
|
|
|
|||
|
|
@ -8,15 +8,16 @@
|
|||
package io.element.android.features.createroom.impl.configureroom
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
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.CreateRoomConfig
|
||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||
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.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
|
|
@ -25,13 +26,18 @@ import io.element.android.libraries.matrix.test.A_THROWABLE
|
|||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||
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.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
|
||||
|
|
@ -56,33 +62,8 @@ class ConfigureRoomPresenterTest {
|
|||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private lateinit var presenter: ConfigureRoomPresenter
|
||||
private lateinit var userListDataStore: UserListDataStore
|
||||
private lateinit var createRoomDataStore: CreateRoomDataStore
|
||||
private lateinit var fakeMatrixClient: FakeMatrixClient
|
||||
private lateinit var fakePickerProvider: FakePickerProvider
|
||||
private lateinit var fakeMediaPreProcessor: FakeMediaPreProcessor
|
||||
private lateinit var fakeAnalyticsService: FakeAnalyticsService
|
||||
private lateinit var fakePermissionsPresenter: FakePermissionsPresenter
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
fakeMatrixClient = FakeMatrixClient()
|
||||
userListDataStore = UserListDataStore()
|
||||
createRoomDataStore = CreateRoomDataStore(userListDataStore)
|
||||
fakePickerProvider = FakePickerProvider()
|
||||
fakeMediaPreProcessor = FakeMediaPreProcessor()
|
||||
fakeAnalyticsService = FakeAnalyticsService()
|
||||
fakePermissionsPresenter = FakePermissionsPresenter()
|
||||
presenter = ConfigureRoomPresenter(
|
||||
dataStore = createRoomDataStore,
|
||||
matrixClient = fakeMatrixClient,
|
||||
mediaPickerProvider = fakePickerProvider,
|
||||
mediaPreProcessor = fakeMediaPreProcessor,
|
||||
analyticsService = fakeAnalyticsService,
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(fakePermissionsPresenter),
|
||||
)
|
||||
|
||||
mockkStatic(File::readBytes)
|
||||
every { any<File>().readBytes() } returns byteArrayOf()
|
||||
}
|
||||
|
|
@ -94,50 +75,56 @@ class ConfigureRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
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.privacy).isEqualTo(RoomPrivacy.Private)
|
||||
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 {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val presenter = createConfigureRoomPresenter()
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
var config = initialState.config
|
||||
assertThat(initialState.isCreateButtonEnabled).isFalse()
|
||||
assertThat(initialState.config.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.isCreateButtonEnabled).isTrue()
|
||||
assertThat(newState.config.isValid).isTrue()
|
||||
|
||||
// Clear room name
|
||||
newState.eventSink(ConfigureRoomEvents.RoomNameChanged(""))
|
||||
newState = awaitItem()
|
||||
config = config.copy(roomName = null)
|
||||
assertThat(newState.config).isEqualTo(config)
|
||||
assertThat(newState.isCreateButtonEnabled).isFalse()
|
||||
assertThat(newState.config.isValid).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - state is updated when fields are changed`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val userListDataStore = UserListDataStore()
|
||||
val pickerProvider = FakePickerProvider()
|
||||
val permissionsPresenter = FakePermissionsPresenter()
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
createRoomDataStore = CreateRoomDataStore(userListDataStore),
|
||||
pickerProvider = pickerProvider,
|
||||
permissionsPresenter = permissionsPresenter,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
var expectedConfig = CreateRoomConfig()
|
||||
assertThat(initialState.config).isEqualTo(expectedConfig)
|
||||
|
||||
|
|
@ -165,22 +152,22 @@ class ConfigureRoomPresenterTest {
|
|||
|
||||
// Room avatar
|
||||
// Pick avatar
|
||||
fakePickerProvider.givenResult(null)
|
||||
pickerProvider.givenResult(null)
|
||||
// From gallery
|
||||
val uriFromGallery = Uri.parse(AN_URI_FROM_GALLERY)
|
||||
fakePickerProvider.givenResult(uriFromGallery)
|
||||
pickerProvider.givenResult(uriFromGallery)
|
||||
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.ChoosePhoto))
|
||||
newState = awaitItem()
|
||||
expectedConfig = expectedConfig.copy(avatarUri = uriFromGallery)
|
||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||
// From camera
|
||||
val uriFromCamera = Uri.parse(AN_URI_FROM_CAMERA)
|
||||
fakePickerProvider.givenResult(uriFromCamera)
|
||||
pickerProvider.givenResult(uriFromCamera)
|
||||
assertThat(newState.cameraPermissionState.permissionGranted).isFalse()
|
||||
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto))
|
||||
newState = awaitItem()
|
||||
assertThat(newState.cameraPermissionState.showDialog).isTrue()
|
||||
fakePermissionsPresenter.setPermissionGranted()
|
||||
permissionsPresenter.setPermissionGranted()
|
||||
newState = awaitItem()
|
||||
assertThat(newState.cameraPermissionState.permissionGranted).isTrue()
|
||||
newState = awaitItem()
|
||||
|
|
@ -188,7 +175,7 @@ class ConfigureRoomPresenterTest {
|
|||
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||
// Do it again, no permission is requested
|
||||
val uriFromCamera2 = Uri.parse(AN_URI_FROM_CAMERA_2)
|
||||
fakePickerProvider.givenResult(uriFromCamera2)
|
||||
pickerProvider.givenResult(uriFromCamera2)
|
||||
newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto))
|
||||
newState = awaitItem()
|
||||
expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera2)
|
||||
|
|
@ -200,13 +187,19 @@ class ConfigureRoomPresenterTest {
|
|||
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||
|
||||
// Room privacy
|
||||
newState.eventSink(ConfigureRoomEvents.RoomPrivacyChanged(RoomPrivacy.Public))
|
||||
newState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public))
|
||||
newState = awaitItem()
|
||||
expectedConfig = expectedConfig.copy(privacy = RoomPrivacy.Public)
|
||||
expectedConfig = expectedConfig.copy(
|
||||
roomVisibility = RoomVisibilityState.Public(
|
||||
roomAddress = RoomAddress.AutoFilled(expectedConfig.roomName ?: ""),
|
||||
roomAddressErrorState = RoomAddressErrorState.None,
|
||||
roomAccess = RoomAccess.Anyone,
|
||||
)
|
||||
)
|
||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||
|
||||
// Remove user
|
||||
newState.eventSink(ConfigureRoomEvents.RemoveFromSelection(selectedUser1))
|
||||
newState.eventSink(ConfigureRoomEvents.RemoveUserFromSelection(selectedUser1))
|
||||
newState = awaitItem()
|
||||
expectedConfig = expectedConfig.copy(invites = expectedConfig.invites.minus(selectedUser1).toImmutableList())
|
||||
assertThat(newState.config).isEqualTo(expectedConfig)
|
||||
|
|
@ -215,15 +208,17 @@ class ConfigureRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - trigger create room action`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val matrixClient = createMatrixClient()
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
matrixClient = matrixClient
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
val createRoomResult = Result.success(RoomId("!createRoomResult:domain"))
|
||||
|
||||
fakeMatrixClient.givenCreateRoomResult(createRoomResult)
|
||||
matrixClient.givenCreateRoomResult(createRoomResult)
|
||||
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
|
||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
val stateAfterCreateRoom = awaitItem()
|
||||
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
|
|
@ -233,18 +228,22 @@ class ConfigureRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - record analytics when creating room`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
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"))
|
||||
|
||||
fakeMatrixClient.givenCreateRoomResult(createRoomResult)
|
||||
matrixClient.givenCreateRoomResult(createRoomResult)
|
||||
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
|
||||
skipItems(2)
|
||||
|
||||
val analyticsEvent = fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>().firstOrNull()
|
||||
val analyticsEvent = analyticsService.capturedEvents.filterIsInstance<CreatedRoom>().firstOrNull()
|
||||
assertThat(analyticsEvent).isNotNull()
|
||||
assertThat(analyticsEvent?.isDM).isFalse()
|
||||
}
|
||||
|
|
@ -252,23 +251,31 @@ class ConfigureRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - trigger create room with upload error and retry`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val matrixClient = createMatrixClient()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||
val createRoomDataStore = CreateRoomDataStore(UserListDataStore())
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
createRoomDataStore = createRoomDataStore,
|
||||
mediaPreProcessor = mediaPreProcessor,
|
||||
matrixClient = matrixClient,
|
||||
analyticsService = analyticsService
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
createRoomDataStore.setAvatarUri(Uri.parse(AN_URI_FROM_GALLERY))
|
||||
fakeMediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(mockk(), mockk(), mockk())))
|
||||
fakeMatrixClient.givenUploadMediaResult(Result.failure(A_THROWABLE))
|
||||
skipItems(1)
|
||||
mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(mockk(), mockk(), mockk())))
|
||||
matrixClient.givenUploadMediaResult(Result.failure(A_THROWABLE))
|
||||
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom)
|
||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
val stateAfterCreateRoom = awaitItem()
|
||||
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
|
||||
assertThat(analyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
|
||||
|
||||
fakeMatrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL))
|
||||
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||
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)
|
||||
|
|
@ -277,23 +284,25 @@ class ConfigureRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - trigger retry and cancel actions`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
val fakeMatrixClient = createMatrixClient()
|
||||
val presenter = createConfigureRoomPresenter(
|
||||
matrixClient = fakeMatrixClient
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = initialState()
|
||||
val createRoomResult = Result.failure<RoomId>(A_THROWABLE)
|
||||
|
||||
fakeMatrixClient.givenCreateRoomResult(createRoomResult)
|
||||
|
||||
// Create
|
||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||
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(initialState.config))
|
||||
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom)
|
||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
val stateAfterRetry = awaitItem()
|
||||
|
|
@ -305,4 +314,33 @@ class ConfigureRoomPresenterTest {
|
|||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun TurbineTestContext<ConfigureRoomState>.initialState(): ConfigureRoomState {
|
||||
skipItems(1)
|
||||
return awaitItem()
|
||||
}
|
||||
|
||||
private fun createMatrixClient() = FakeMatrixClient(
|
||||
userIdServerNameLambda = { "matrix.org" },
|
||||
)
|
||||
|
||||
private fun createConfigureRoomPresenter(
|
||||
createRoomDataStore: CreateRoomDataStore = CreateRoomDataStore(UserListDataStore()),
|
||||
matrixClient: MatrixClient = createMatrixClient(),
|
||||
pickerProvider: PickerProvider = FakePickerProvider(),
|
||||
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
isKnockFeatureEnabled: Boolean = true,
|
||||
) = ConfigureRoomPresenter(
|
||||
dataStore = createRoomDataStore,
|
||||
matrixClient = matrixClient,
|
||||
mediaPickerProvider = pickerProvider,
|
||||
mediaPreProcessor = mediaPreProcessor,
|
||||
analyticsService = analyticsService,
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
|
||||
featureFlagService = FakeFeatureFlagService(
|
||||
mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,5 +124,12 @@ enum class FeatureFlags(
|
|||
" You'll have to stop and re-open the app manually for that setting to take effect.",
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
)
|
||||
),
|
||||
Knock(
|
||||
key = "feature.knock",
|
||||
title = "Ask to join",
|
||||
description = "Allow creating rooms which users can request access to.",
|
||||
defaultValue = { false },
|
||||
isFinished = false,
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.libraries.matrix.api.createroom
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import java.util.Optional
|
||||
|
||||
data class CreateRoomParameters(
|
||||
val name: String?,
|
||||
|
|
@ -18,4 +19,6 @@ data class CreateRoomParameters(
|
|||
val preset: RoomPreset,
|
||||
val invite: List<UserId>? = null,
|
||||
val avatar: String? = null,
|
||||
val joinRuleOverride: JoinRuleOverride = JoinRuleOverride.None,
|
||||
val canonicalAlias: Optional<String> = Optional.empty(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.createroom
|
||||
|
||||
/**
|
||||
* Rules to override the default room join rules.
|
||||
*/
|
||||
sealed interface JoinRuleOverride {
|
||||
data object Knock : JoinRuleOverride
|
||||
data object None : JoinRuleOverride
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
|||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
|
||||
import io.element.android.libraries.matrix.api.createroom.JoinRuleOverride
|
||||
import io.element.android.libraries.matrix.api.createroom.RoomPreset
|
||||
import io.element.android.libraries.matrix.api.createroom.RoomVisibility
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
|
|
@ -32,6 +33,7 @@ import io.element.android.libraries.matrix.api.pusher.PushersService
|
|||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.PendingRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
|
|
@ -108,6 +110,7 @@ import kotlin.jvm.optionals.getOrNull
|
|||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters
|
||||
import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule
|
||||
import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset
|
||||
import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
|
||||
import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService
|
||||
|
|
@ -304,14 +307,33 @@ class RustMatrixClient(
|
|||
RoomVisibility.PUBLIC -> RustRoomVisibility.PUBLIC
|
||||
RoomVisibility.PRIVATE -> RustRoomVisibility.PRIVATE
|
||||
},
|
||||
preset = when (createRoomParams.preset) {
|
||||
RoomPreset.PRIVATE_CHAT -> RustRoomPreset.PRIVATE_CHAT
|
||||
RoomPreset.PUBLIC_CHAT -> RustRoomPreset.PUBLIC_CHAT
|
||||
RoomPreset.TRUSTED_PRIVATE_CHAT -> RustRoomPreset.TRUSTED_PRIVATE_CHAT
|
||||
preset = when (createRoomParams.visibility) {
|
||||
RoomVisibility.PRIVATE -> {
|
||||
if (createRoomParams.isDirect) {
|
||||
RustRoomPreset.TRUSTED_PRIVATE_CHAT
|
||||
} else {
|
||||
RustRoomPreset.PRIVATE_CHAT
|
||||
}
|
||||
}
|
||||
RoomVisibility.PUBLIC -> {
|
||||
RustRoomPreset.PUBLIC_CHAT
|
||||
}
|
||||
},
|
||||
invite = createRoomParams.invite?.map { it.value },
|
||||
avatar = createRoomParams.avatar,
|
||||
powerLevelContentOverride = defaultRoomCreationPowerLevels,
|
||||
powerLevelContentOverride = defaultRoomCreationPowerLevels.copy(
|
||||
invite = if (createRoomParams.joinRuleOverride == JoinRuleOverride.Knock) {
|
||||
// override the invite power level so it's the same as kick.
|
||||
RoomMember.Role.MODERATOR.powerLevel.toInt()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
),
|
||||
joinRuleOverride = when (createRoomParams.joinRuleOverride) {
|
||||
JoinRuleOverride.Knock -> RustJoinRule.Knock
|
||||
JoinRuleOverride.None -> null
|
||||
},
|
||||
canonicalAlias = createRoomParams.canonicalAlias.getOrNull(),
|
||||
)
|
||||
val roomId = RoomId(client.createRoom(rustParams))
|
||||
// Wait to receive the room back from the sync but do not returns failure if it fails.
|
||||
|
|
|
|||
|
|
@ -284,9 +284,6 @@
|
|||
<string name="invite_friends_text">"Гэй, пагавары са мной у %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Паведаміць аб памылцы з дапамогай Rageshake"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Хто заўгодна"</string>
|
||||
<string name="screen_create_room_access_section_header">"Доступ у пакой"</string>
|
||||
<string name="screen_create_room_access_section_knocking_option_title">"Папрасіце далучыцца"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Не ўдалося выбраць носьбіт, паўтарыце спробу."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз."</string>
|
||||
|
|
|
|||
|
|
@ -295,14 +295,6 @@ Důvod: %1$s."</string>
|
|||
<string name="invite_friends_text">"Ahoj, ozvi se mi na %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Zatřeste zařízením pro nahlášení chyby"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Do této místnosti může vstoupit kdokoli"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Kdokoliv"</string>
|
||||
<string name="screen_create_room_access_section_header">"Přístup do místnosti"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Viditelnost místnosti"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Výběr média se nezdařil, zkuste to prosím znovu."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Nahrání média se nezdařilo, zkuste to prosím znovu."</string>
|
||||
|
|
|
|||
|
|
@ -285,11 +285,6 @@
|
|||
<string name="invite_friends_text">"Γεια, μίλα μου στην εφαρμογή %1$s :%2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Κούνησε δυνατά τη συσκευή σου για να αναφέρεις κάποιο σφάλμα"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Οποιοσδήποτε μπορεί να συμμετάσχει σε αυτό το δωμάτιο"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Οποιοσδήποτε"</string>
|
||||
<string name="screen_create_room_access_section_header">"Πρόσβαση Δωματίου"</string>
|
||||
<string name="screen_create_room_access_section_knocking_option_description">"Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στο δωμάτιο, αλλά ένας διαχειριστής ή συντονιστής θα πρέπει να αποδεχθεί το αίτημα"</string>
|
||||
<string name="screen_create_room_access_section_knocking_option_title">"Αίτημα συμμετοχής"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Αποτυχία επιλογής πολυμέσου, δοκίμασε ξανά."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Αποτυχία μεταφόρτωσης μέσου, δοκίμασε ξανά."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Αποτυχία μεταφόρτωσης πολυμέσων, δοκίμασε ξανά."</string>
|
||||
|
|
|
|||
|
|
@ -291,14 +291,6 @@ Põhjus: %1$s."</string>
|
|||
<string name="invite_friends_text">"Hei, suhtle minuga %1$s võrgus: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Veast teatamiseks raputa nutiseadet ägedalt"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Kõik võivad selle jututoaga liituda"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Kõik"</string>
|
||||
<string name="screen_create_room_access_section_header">"Ligipääs jututoale"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Jututoa nähtavus"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Meediafaili valimine ei õnnestunud. Palun proovi uuesti."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Meediafaili töötlemine enne üleslaadimist ei õnnestunud. Palun proovi uuesti."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Meediafaili üleslaadimine ei õnnestunud. Palun proovi uuesti."</string>
|
||||
|
|
|
|||
|
|
@ -290,14 +290,6 @@ Raison: %1$s."</string>
|
|||
<string name="invite_friends_text">"Salut, parle-moi sur %1$s : %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Rageshake pour signaler un problème"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Tout le monde peut rejoindre ce salon"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Tout le monde"</string>
|
||||
<string name="screen_create_room_access_section_header">"Accès au salon"</string>
|
||||
<string name="screen_create_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_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 d’une adresse de salon."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Adresse du salon"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Visibilité du salon"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Échec de la sélection du média, veuillez réessayer."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Échec du traitement des médias à télécharger, veuillez réessayer."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Échec du téléchargement du média, veuillez réessayer."</string>
|
||||
|
|
|
|||
|
|
@ -291,14 +291,6 @@ Ok: %1$s."</string>
|
|||
<string name="invite_friends_text">"Beszélgessünk itt: %1$s, %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Az eszköz rázása a hibajelentéshez"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Bárki csatlakozhat ehhez a szobához"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Bárki"</string>
|
||||
<string name="screen_create_room_access_section_header">"Szobahozzáférés"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Szoba láthatósága"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Nem sikerült kiválasztani a médiát, próbálja újra."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Nem sikerült a média feltöltése, próbálja újra."</string>
|
||||
|
|
|
|||
|
|
@ -287,14 +287,6 @@ Alasan: %1$s."</string>
|
|||
<string name="invite_friends_text">"Hai, bicaralah dengan saya di %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Rageshake untuk melaporkan kutu"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Siapa pun dapat bergabung dengan ruangan ini"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Siapa pun"</string>
|
||||
<string name="screen_create_room_access_section_header">"Akses Ruangan"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Keterlihatan ruangan"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Gagal memilih media, silakan coba lagi."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Gagal memproses media untuk diunggah, silakan coba lagi."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Gagal mengunggah media, silakan coba lagi."</string>
|
||||
|
|
|
|||
|
|
@ -283,11 +283,6 @@ Reden: %1$s."</string>
|
|||
<string name="invite_friends_text">"Hé, praat met me op %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Schudden om een bug te melden"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Iedereen kan toetreden tot deze kamer"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Iedereen"</string>
|
||||
<string name="screen_create_room_access_section_header">"Toegang tot de kamer"</string>
|
||||
<string name="screen_create_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_access_section_knocking_option_title">"Vraag om toe te treden"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Het selecteren van media is mislukt. Probeer het opnieuw."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Het verwerken van media voor uploaden is mislukt. Probeer het opnieuw."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Het uploaden van media is mislukt. Probeer het opnieuw."</string>
|
||||
|
|
|
|||
|
|
@ -292,14 +292,6 @@ Powód: %1$s."</string>
|
|||
<string name="invite_friends_text">"Hej, porozmawiajmy na %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Wstrząśnij gniewnie, aby zgłosić błąd"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Każdy może dołączyć do tego pokoju"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Wszyscy"</string>
|
||||
<string name="screen_create_room_access_section_header">"Dostęp do pokoju"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Widoczność pomieszczenia"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Nie udało się wybrać multimediów. Spróbuj ponownie."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Przetwarzanie multimediów do przesłania nie powiodło się, spróbuj ponownie."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Przesyłanie multimediów nie powiodło się, spróbuj ponownie."</string>
|
||||
|
|
|
|||
|
|
@ -291,14 +291,6 @@ Razão: %1$s."</string>
|
|||
<string name="invite_friends_text">"Alô! Fala comigo na %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Agita o dispositivo em fúria para comunicar um problema"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Qualquer pessoa pode entrar nesta sala"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Qualquer pessoa"</string>
|
||||
<string name="screen_create_room_access_section_header">"Acesso à sala"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Visibilidade da sala"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Falha ao selecionar multimédia, por favor tente novamente."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Falha ao processar multimédia para carregamento, por favor tente novamente."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Falhar ao carregar multimédia, por favor tente novamente."</string>
|
||||
|
|
|
|||
|
|
@ -295,14 +295,6 @@
|
|||
<string name="invite_friends_text">"Привет, поговори со мной по %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Встряхните устройство, чтобы сообщить об ошибке"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Любой желающий может присоединиться к этой комнате"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Любой"</string>
|
||||
<string name="screen_create_room_access_section_header">"Доступ в комнату"</string>
|
||||
<string name="screen_create_room_access_section_knocking_option_description">"Любой желающий может подать заявку на присоединение к комнате, но администратор или модератор должен будет принять запрос."</string>
|
||||
<string name="screen_create_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_visibility_section_title">"Видимость комнаты"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Не удалось выбрать носитель, попробуйте еще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</string>
|
||||
|
|
|
|||
|
|
@ -293,14 +293,6 @@ Dôvod: %1$s."</string>
|
|||
<string name="invite_friends_text">"Ahoj, porozprávajte sa so mnou na %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Zúrivo potriasť pre nahlásenie chyby"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Do tejto miestnosti sa môže pripojiť ktokoľvek"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Ktokoľvek"</string>
|
||||
<string name="screen_create_room_access_section_header">"Prístup do miestnosti"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Viditeľnosť miestnosti"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Nepodarilo sa vybrať médium, skúste to prosím znova."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Nepodarilo sa nahrať médiá, skúste to prosím znova."</string>
|
||||
|
|
|
|||
|
|
@ -291,14 +291,6 @@ Reason: %1$s."</string>
|
|||
<string name="invite_friends_text">"Hey, talk to me on %1$s: %2$s"</string>
|
||||
<string name="login_initial_device_name_android">"%1$s Android"</string>
|
||||
<string name="preference_rageshake">"Rageshake to report bug"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_description">"Anyone can join this room"</string>
|
||||
<string name="screen_create_room_access_section_anyone_option_title">"Anyone"</string>
|
||||
<string name="screen_create_room_access_section_header">"Room Access"</string>
|
||||
<string name="screen_create_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_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_visibility_section_title">"Room visibility"</string>
|
||||
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>
|
||||
<string name="screen_media_upload_preview_error_failed_sending">"Failed uploading media, please try again."</string>
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5ef1c5aa8a1be3fc5c2fd55b8fde64f7ec7c3d12037e4e2b08049c6a1ea21a3d
|
||||
size 30128
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5f5762dcb065b0406441e7f67d2b3a120f11bed71d1f56b96cb334b831d96ce3
|
||||
size 29511
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8344c5a707a3af3aaeab22006e84bd93f1cdf8389199d88beb276a8b672b0a4a
|
||||
size 50817
|
||||
oid sha256:e7de44cc8f686264788aa79cde24b9896c6ca4dabc49b246abbd1d3dd0311e25
|
||||
size 57891
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2c39135c177aa7b22c9451ef86bee41a216931759448ed2764d3bef4ea410230
|
||||
size 72685
|
||||
oid sha256:2df49a9c82438dbf1ba0ba0670cadd8e7bb47a7e25de27793fd756760cf7c5f2
|
||||
size 79562
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2df49a9c82438dbf1ba0ba0670cadd8e7bb47a7e25de27793fd756760cf7c5f2
|
||||
size 79562
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b05e067c92747ac92f10efcec20df0a1a7e1040c0be7a4b04009a5cf50c0aeb5
|
||||
size 49720
|
||||
oid sha256:c754daf307554dc0c83ba0f89c38f5fa7f5c2edff82907b0826c2b781498dbcb
|
||||
size 56047
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:79b1423947e9f87d9139f1742f845a80d6474c0b36d3557b1c30fee6a3e808ad
|
||||
size 71946
|
||||
oid sha256:b6e43161abdccacbede4a8c4b4ff446026035df4be0894a55c6364681458bb2f
|
||||
size 78805
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b6e43161abdccacbede4a8c4b4ff446026035df4be0894a55c6364681458bb2f
|
||||
size 78805
|
||||
|
|
@ -61,6 +61,7 @@
|
|||
"name" : ":features:createroom:impl",
|
||||
"includeRegex" : [
|
||||
"screen_create_room_.*",
|
||||
"screen\\.create_room\\..*",
|
||||
"screen_start_chat_.*"
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue