First Commit

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

View File

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

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerRenderer
import io.element.android.libraries.di.RoomScope
@ContributesBinding(RoomScope::class)
class DefaultKnockRequestsBannerRenderer(
private val presenter: KnockRequestsBannerPresenter,
) : KnockRequestsBannerRenderer {
@Composable
override fun View(modifier: Modifier, onViewRequestsClick: () -> Unit) {
val state = presenter.present()
KnockRequestsBannerView(
state = state,
onViewRequestsClick = onViewRequestsClick,
)
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
sealed interface KnockRequestsBannerEvents {
data object AcceptSingleRequest : KnockRequestsBannerEvents
data object Dismiss : KnockRequestsBannerEvents
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import dev.zacsweers.metro.Inject
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
private const val ACCEPT_ERROR_DISPLAY_DURATION = 1500L
@Inject
class KnockRequestsBannerPresenter(
private val knockRequestsService: KnockRequestsService,
@SessionCoroutineScope
private val sessionCoroutineScope: CoroutineScope,
) : Presenter<KnockRequestsBannerState> {
@Composable
override fun present(): KnockRequestsBannerState {
val knockRequests by remember {
knockRequestsService.knockRequestsFlow.mapState { knockRequests ->
knockRequests.dataOrNull().orEmpty()
.filter { !it.isSeen }
.toImmutableList()
}
}.collectAsState()
val permissions by knockRequestsService.permissionsFlow.collectAsState()
val showAcceptError = remember { mutableStateOf(false) }
val shouldShowBanner by remember {
derivedStateOf {
permissions.canHandle && knockRequests.isNotEmpty()
}
}
fun handleEvent(event: KnockRequestsBannerEvents) {
when (event) {
is KnockRequestsBannerEvents.AcceptSingleRequest -> {
sessionCoroutineScope.acceptSingleKnockRequest(
knockRequests = knockRequests,
displayAcceptError = showAcceptError,
)
}
is KnockRequestsBannerEvents.Dismiss -> {
sessionCoroutineScope.launch {
knockRequestsService.markAllKnockRequestsAsSeen()
}
}
}
}
return KnockRequestsBannerState(
knockRequests = knockRequests,
displayAcceptError = showAcceptError.value,
canAccept = permissions.canAccept,
isVisible = shouldShowBanner,
eventSink = ::handleEvent,
)
}
private fun CoroutineScope.acceptSingleKnockRequest(
knockRequests: List<KnockRequestPresentable>,
displayAcceptError: MutableState<Boolean>,
) = launch {
val knockRequest = knockRequests.singleOrNull()
if (knockRequest != null) {
knockRequestsService.acceptKnockRequest(knockRequest, optimistic = true)
.onFailure {
displayAcceptError.value = true
delay(ACCEPT_ERROR_DISPLAY_DURATION)
displayAcceptError.value = false
}
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import kotlinx.collections.immutable.ImmutableList
data class KnockRequestsBannerState(
val isVisible: Boolean,
val knockRequests: ImmutableList<KnockRequestPresentable>,
val displayAcceptError: Boolean,
val canAccept: Boolean,
val eventSink: (KnockRequestsBannerEvents) -> Unit,
) {
val subtitle = knockRequests.singleOrNull()?.userId?.value
val reason = knockRequests.singleOrNull()?.reason
@Composable
fun formattedTitle(): String {
return when (knockRequests.size) {
0 -> ""
1 -> stringResource(R.string.screen_room_single_knock_request_title, knockRequests.first().getBestName())
else -> {
val firstRequest = knockRequests.first()
val otherRequestsCount = knockRequests.size - 1
pluralStringResource(
id = R.plurals.screen_room_multiple_knock_requests_title,
count = otherRequestsCount,
firstRequest.getBestName(),
otherRequestsCount
)
}
}
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
import kotlinx.collections.immutable.toImmutableList
class KnockRequestsBannerStateProvider : PreviewParameterProvider<KnockRequestsBannerState> {
override val values: Sequence<KnockRequestsBannerState>
get() = sequenceOf(
aKnockRequestsBannerState(),
aKnockRequestsBannerState(
knockRequests = listOf(
aKnockRequestPresentable(
reason = "A very long reason that should probably be truncated, " +
"but could be also expanded so you can see it over the lines, wow," +
"very amazing reason, I know, right, I'm so good at writing reasons."
)
)
),
aKnockRequestsBannerState(
knockRequests = listOf(
aKnockRequestPresentable(),
aKnockRequestPresentable(displayName = "Alice")
)
),
aKnockRequestsBannerState(
knockRequests = listOf(
aKnockRequestPresentable(),
aKnockRequestPresentable(displayName = "Alice"),
aKnockRequestPresentable(displayName = "Bob"),
aKnockRequestPresentable(displayName = "Charlie")
)
),
aKnockRequestsBannerState(
canAccept = false
),
aKnockRequestsBannerState(
displayAcceptError = true
),
aKnockRequestsBannerState(
knockRequests = listOf(
aKnockRequestPresentable(
displayName = "A_very_long_display_name_so_that_the_text_can_be_displayed_on_multiple_lines"
)
)
),
)
}
fun aKnockRequestsBannerState(
knockRequests: List<KnockRequestPresentable> = listOf(aKnockRequestPresentable()),
displayAcceptError: Boolean = false,
canAccept: Boolean = true,
isVisible: Boolean = true,
eventSink: (KnockRequestsBannerEvents) -> Unit = {}
) = KnockRequestsBannerState(
knockRequests = knockRequests.toImmutableList(),
displayAcceptError = displayAcceptError,
canAccept = canAccept,
isVisible = isVisible,
eventSink = eventSink,
)

View File

@@ -0,0 +1,235 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarRow
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
private const val MAX_AVATAR_COUNT = 3
@Composable
fun KnockRequestsBannerView(
state: KnockRequestsBannerState,
onViewRequestsClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
AnimatedVisibility(
visible = state.isVisible,
enter = expandVertically(),
exit = shrinkVertically(),
) {
Surface(
shape = MaterialTheme.shapes.small,
color = ElementTheme.colors.bgCanvasDefaultLevel1,
shadowElevation = 24.dp,
modifier = Modifier.padding(16.dp),
) {
KnockRequestsBannerContent(
state = state,
onViewRequestsClick = onViewRequestsClick,
)
}
}
KnockRequestsAcceptErrorView(displayError = state.displayAcceptError)
}
}
@Composable
private fun KnockRequestsAcceptErrorView(
displayError: Boolean,
modifier: Modifier = Modifier,
) {
val asyncIndicatorState = rememberAsyncIndicatorState()
AsyncIndicatorHost(modifier = modifier.statusBarsPadding(), state = asyncIndicatorState)
LaunchedEffect(displayError) {
if (displayError) {
asyncIndicatorState.enqueue {
AsyncIndicator.Custom(text = stringResource(CommonStrings.error_unknown))
}
} else {
asyncIndicatorState.clear()
}
}
}
@Composable
private fun KnockRequestsBannerContent(
state: KnockRequestsBannerState,
onViewRequestsClick: () -> Unit,
modifier: Modifier = Modifier,
) {
fun onDismissClick() {
state.eventSink(KnockRequestsBannerEvents.Dismiss)
}
fun onAcceptClick() {
state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest)
}
Column(
modifier
.fillMaxWidth()
.padding(all = 16.dp)
) {
Row {
KnockRequestAvatarView(
state.knockRequests,
modifier = Modifier.padding(top = 2.dp),
)
Spacer(modifier = Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = state.formattedTitle(),
style = ElementTheme.typography.fontBodyMdMedium,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Start,
)
if (state.subtitle != null) {
Text(
text = state.subtitle,
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
textAlign = TextAlign.Start,
)
}
}
Spacer(modifier = Modifier.width(4.dp))
Icon(
modifier = Modifier.clickable(onClick = ::onDismissClick),
imageVector = CompoundIcons.Close(),
contentDescription = stringResource(CommonStrings.action_close)
)
}
val reason = state.reason
if (!reason.isNullOrEmpty()) {
Spacer(modifier = Modifier.height(16.dp))
Text(
text = state.reason,
color = ElementTheme.colors.textPrimary,
style = ElementTheme.typography.fontBodyMdRegular,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.height(16.dp))
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
if (state.knockRequests.size > 1) {
Button(
text = stringResource(R.string.screen_room_multiple_knock_requests_view_all_button_title),
onClick = onViewRequestsClick,
size = ButtonSize.MediumLowPadding,
modifier = Modifier.weight(1f),
)
} else {
OutlinedButton(
text = stringResource(R.string.screen_room_single_knock_request_view_button_title),
onClick = onViewRequestsClick,
size = ButtonSize.MediumLowPadding,
modifier = Modifier.weight(1f),
)
if (state.canAccept) {
Button(
text = stringResource(R.string.screen_room_single_knock_request_accept_button_title),
onClick = ::onAcceptClick,
size = ButtonSize.MediumLowPadding,
modifier = Modifier.weight(1f),
)
}
}
}
}
}
@Composable
private fun KnockRequestAvatarView(
knockRequests: ImmutableList<KnockRequestPresentable>,
modifier: Modifier = Modifier,
) {
Box(modifier) {
when (knockRequests.size) {
0 -> Unit
1 -> Avatar(
avatarData = knockRequests.first().getAvatarData(AvatarSize.KnockRequestBanner),
avatarType = AvatarType.User,
)
else -> KnockRequestAvatarListView(knockRequests)
}
}
}
@Composable
private fun KnockRequestAvatarListView(
knockRequests: ImmutableList<KnockRequestPresentable>,
modifier: Modifier = Modifier,
) {
val avatars = knockRequests
.take(MAX_AVATAR_COUNT)
.map { knockRequest ->
knockRequest.getAvatarData(AvatarSize.KnockRequestBanner)
}
.toImmutableList()
AvatarRow(
avatarDataList = avatars,
avatarType = AvatarType.User,
modifier = modifier,
)
}
@Composable
@PreviewsDayNight
internal fun KnockRequestsBannerViewPreview(@PreviewParameter(KnockRequestsBannerStateProvider::class) state: KnockRequestsBannerState) = ElementPreview {
KnockRequestsBannerView(
state = state,
onViewRequestsClick = {},
)
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
fun aKnockRequestPresentable(
eventId: EventId = EventId("\$eventId"),
userId: UserId = UserId("@jacob_ross:example.com"),
displayName: String? = "Jacob Ross",
avatarUrl: String? = null,
reason: String? = "Hi, I would like to get access to this room please.",
formattedDate: String? = "20 Nov 2024",
) = object : KnockRequestPresentable {
override val eventId: EventId = eventId
override val userId: UserId = userId
override val displayName: String? = displayName
override val avatarUrl: String? = avatarUrl
override val reason: String? = reason
override val formattedDate: String? = formattedDate
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
data class KnockRequestPermissions(
val canAccept: Boolean,
val canDecline: Boolean,
val canBan: Boolean,
) {
val canHandle = canAccept || canDecline || canBan
}
fun JoinedRoom.knockRequestPermissionsFlow(): Flow<KnockRequestPermissions> {
return syncUpdateFlow.map {
val canAccept = canInvite().getOrDefault(false)
val canDecline = canKick().getOrDefault(false)
val canBan = canBan().getOrDefault(false)
KnockRequestPermissions(canAccept, canDecline, canBan)
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
import androidx.compose.runtime.Immutable
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
@Immutable
interface KnockRequestPresentable {
val eventId: EventId
val userId: UserId
val displayName: String?
val avatarUrl: String?
val reason: String?
val formattedDate: String?
fun getAvatarData(size: AvatarSize) = AvatarData(
id = userId.value,
name = displayName,
url = avatarUrl,
size = size,
)
fun getBestName(): String {
return displayName?.takeIf { it.isNotEmpty() } ?: userId.value
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
class KnockRequestWrapper(
private val inner: KnockRequest,
dateFormatter: (Long?) -> String? = { null }
) : KnockRequestPresentable {
override val eventId: EventId = inner.eventId
override val userId: UserId = inner.userId
override val displayName: String? = inner.displayName
override val avatarUrl: String? = inner.avatarUrl
override val reason: String? = inner.reason?.trim()
override val formattedDate: String? = dateFormatter(inner.timestamp)
val isSeen: Boolean = inner.isSeen
suspend fun accept(): Result<Unit> = inner.accept()
suspend fun decline(reason: String?): Result<Unit> = inner.decline(reason)
suspend fun declineAndBan(reason: String?): Result<Unit> = inner.declineAndBan(reason)
suspend fun markAsSeen(): Result<Unit> = inner.markAsSeen()
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
sealed class KnockRequestsException : Exception() {
data object AcceptAllPartiallyFailed : KnockRequestsException()
data object KnockRequestNotFound : KnockRequestsException()
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.SingleIn
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.room.JoinedRoom
@BindingContainer
@ContributesTo(RoomScope::class)
object KnockRequestsModule {
@Provides
@SingleIn(RoomScope::class)
fun knockRequestsService(room: JoinedRoom, featureFlagService: FeatureFlagService): KnockRequestsService {
return KnockRequestsService(
knockRequestsFlow = room.knockRequestsFlow,
permissionsFlow = room.knockRequestPermissionsFlow(),
isKnockFeatureEnabledFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock),
coroutineScope = room.roomCoroutineScope
)
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.data
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.supervisorScope
class KnockRequestsService(
knockRequestsFlow: Flow<List<KnockRequest>>,
permissionsFlow: Flow<KnockRequestPermissions>,
isKnockFeatureEnabledFlow: Flow<Boolean>,
coroutineScope: CoroutineScope,
) {
// Keep track of the knock requests that have been handled, so we don't have to wait for sync to remove them.
private val handledKnockRequestIds = MutableStateFlow<Set<EventId>>(emptySet())
val knockRequestsFlow = combine(
isKnockFeatureEnabledFlow,
knockRequestsFlow,
handledKnockRequestIds,
) { isKnockEnabled, knockRequests, handledKnockIds ->
if (!isKnockEnabled) {
AsyncData.Success(persistentListOf())
} else {
val presentableKnockRequests = knockRequests
.filter { it.eventId !in handledKnockIds }
.map { inner -> KnockRequestWrapper(inner) }
.toImmutableList()
AsyncData.Success(presentableKnockRequests)
}
}.stateIn(coroutineScope, SharingStarted.Lazily, AsyncData.Loading())
val permissionsFlow = permissionsFlow.stateIn(
scope = coroutineScope,
started = SharingStarted.Lazily,
initialValue = KnockRequestPermissions(canAccept = false, canDecline = false, canBan = false)
)
private fun knockRequestsList() = knockRequestsFlow.value.dataOrNull().orEmpty()
private fun getKnockRequestById(eventId: EventId): KnockRequestWrapper? {
return knockRequestsList().find { it.eventId == eventId }
}
/**
* Accept a knock request.
* @param knockRequest The knock request to accept.
* @param optimistic If true, the request will be marked as handled before the server responds.
*/
suspend fun acceptKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result<Unit> {
val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult()
return handleKnockRequest(wrapped, optimistic) { accept() }
}
/**
* Decline a knock request.
* @param knockRequest The knock request to decline.
* @param optimistic If true, the request will be marked as handled before the server responds.
*/
suspend fun declineKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result<Unit> {
val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult()
return handleKnockRequest(wrapped, optimistic) { decline(null) }
}
/**
* Decline a knock request by banning the user.
* @param knockRequest The knock request to decline.
* @param optimistic If true, the request will be marked as handled before the server responds.
*/
suspend fun declineAndBanKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result<Unit> {
val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult()
return handleKnockRequest(wrapped, optimistic) { declineAndBan(null) }
}
/**
* Accept all currently known knock requests.
* @param optimistic If true, the requests will be marked as handled before the server responds.
*/
suspend fun acceptAllKnockRequests(optimistic: Boolean = false): Result<Unit> = supervisorScope {
val results = knockRequestsList()
.map { knockRequest ->
async {
acceptKnockRequest(knockRequest, optimistic = optimistic)
}
}
.awaitAll()
if (results.all { it.isSuccess }) {
Result.success(Unit)
} else {
Result.failure(KnockRequestsException.AcceptAllPartiallyFailed)
}
}
/**
* Mark all currently known knock requests as seen.
*/
suspend fun markAllKnockRequestsAsSeen() = supervisorScope {
knockRequestsList()
.map { knockRequest ->
async { knockRequest.markAsSeen() }
}
.awaitAll()
}
private suspend fun handleKnockRequest(
knockRequest: KnockRequestWrapper,
optimistic: Boolean,
action: suspend (KnockRequestWrapper.() -> Result<Unit>)
): Result<Unit> {
if (optimistic) {
handledKnockRequestIds.getAndUpdate { it + knockRequest.eventId }
}
return action(knockRequest)
.onFailure {
if (optimistic) {
handledKnockRequestIds.getAndUpdate { it - knockRequest.eventId }
}
}
.onSuccess {
if (!optimistic) {
handledKnockRequestIds.getAndUpdate { it + knockRequest.eventId }
}
}
}
}
private fun knockRequestNotFoundResult() = Result.failure<Unit>(KnockRequestsException.KnockRequestNotFound)

View File

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

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
sealed interface KnockRequestsListEvents {
data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents
data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents
data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents
data object AcceptAll : KnockRequestsListEvents
data object ResetCurrentAction : KnockRequestsListEvents
data object RetryCurrentAction : KnockRequestsListEvents
data object ConfirmCurrentAction : KnockRequestsListEvents
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.libraries.di.RoomScope
@ContributesNode(RoomScope::class)
@AssistedInject
class KnockRequestsListNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: KnockRequestsListPresenter,
) : Node(buildContext, plugins = plugins) {
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
KnockRequestsListView(
state = state,
onBackClick = ::navigateUp,
modifier = modifier
)
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@Inject
class KnockRequestsListPresenter(
private val knockRequestsService: KnockRequestsService,
) : Presenter<KnockRequestsListState> {
@Composable
override fun present(): KnockRequestsListState {
val asyncAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
var currentAction by remember { mutableStateOf<KnockRequestsAction>(KnockRequestsAction.None) }
val permissions by knockRequestsService.permissionsFlow.collectAsState()
val knockRequests by knockRequestsService.knockRequestsFlow.collectAsState()
val coroutineScope = rememberCoroutineScope()
fun handleEvent(event: KnockRequestsListEvents) {
when (event) {
KnockRequestsListEvents.AcceptAll -> {
currentAction = KnockRequestsAction.AcceptAll
}
is KnockRequestsListEvents.Accept -> {
currentAction = KnockRequestsAction.Accept(event.knockRequest)
}
is KnockRequestsListEvents.Decline -> {
currentAction = KnockRequestsAction.Decline(event.knockRequest)
}
is KnockRequestsListEvents.DeclineAndBan -> {
currentAction = KnockRequestsAction.DeclineAndBan(event.knockRequest)
}
KnockRequestsListEvents.ResetCurrentAction -> {
asyncAction.value = AsyncAction.Uninitialized
currentAction = KnockRequestsAction.None
}
KnockRequestsListEvents.RetryCurrentAction -> {
coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true)
}
KnockRequestsListEvents.ConfirmCurrentAction -> {
coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true)
}
}
}
LaunchedEffect(currentAction) {
executeAction(currentAction, asyncAction, isActionConfirmed = false)
}
return KnockRequestsListState(
knockRequests = knockRequests,
currentAction = currentAction,
permissions = permissions,
asyncAction = asyncAction.value,
eventSink = ::handleEvent,
)
}
private fun CoroutineScope.executeAction(
currentAction: KnockRequestsAction,
asyncAction: MutableState<AsyncAction<Unit>>,
isActionConfirmed: Boolean,
) = launch {
when (currentAction) {
is KnockRequestsAction.Accept -> {
runUpdatingState(asyncAction) {
knockRequestsService.acceptKnockRequest(currentAction.knockRequest)
}
}
is KnockRequestsAction.Decline -> {
if (isActionConfirmed) {
runUpdatingState(asyncAction) {
knockRequestsService.declineKnockRequest(currentAction.knockRequest)
}
} else {
asyncAction.value = AsyncAction.ConfirmingNoParams
}
}
is KnockRequestsAction.DeclineAndBan -> {
if (isActionConfirmed) {
runUpdatingState(asyncAction) {
knockRequestsService.declineAndBanKnockRequest(currentAction.knockRequest)
}
} else {
asyncAction.value = AsyncAction.ConfirmingNoParams
}
}
is KnockRequestsAction.AcceptAll -> {
if (isActionConfirmed) {
runUpdatingState(asyncAction) {
knockRequestsService.acceptAllKnockRequests()
}
} else {
asyncAction.value = AsyncAction.ConfirmingNoParams
}
}
KnockRequestsAction.None -> Unit
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import androidx.compose.runtime.Immutable
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import kotlinx.collections.immutable.ImmutableList
data class KnockRequestsListState(
val knockRequests: AsyncData<ImmutableList<KnockRequestPresentable>>,
val currentAction: KnockRequestsAction,
val asyncAction: AsyncAction<Unit>,
val permissions: KnockRequestPermissions,
val eventSink: (KnockRequestsListEvents) -> Unit,
) {
val canAcceptAll = permissions.canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1
}
@Immutable
sealed interface KnockRequestsAction {
data object None : KnockRequestsAction
data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsAction
data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsAction
data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsAction
data object AcceptAll : KnockRequestsAction
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
open class KnockRequestsListStateProvider : PreviewParameterProvider<KnockRequestsListState> {
override val values: Sequence<KnockRequestsListState>
get() = sequenceOf(
aKnockRequestsListState(
knockRequests = AsyncData.Loading(),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf()
),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable()
)
),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable(
reason = "A very long reason that should probably be truncated, " +
"but could be also expanded so you can see it over the lines, wow," +
"very amazing reason, I know, right, I'm so good at writing reasons."
)
)
),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable(),
aKnockRequestPresentable(
userId = UserId("@user:example.com"),
displayName = null,
avatarUrl = null,
reason = null,
)
)
),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable()
)
),
currentAction = KnockRequestsAction.AcceptAll,
asyncAction = AsyncAction.ConfirmingNoParams,
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable()
)
),
currentAction = KnockRequestsAction.AcceptAll,
asyncAction = AsyncAction.Loading,
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable()
)
),
permissions = KnockRequestPermissions(
canAccept = false,
canDecline = true,
canBan = true,
),
currentAction = KnockRequestsAction.AcceptAll,
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable()
)
),
permissions = KnockRequestPermissions(
canAccept = true,
canDecline = false,
canBan = true,
),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable()
)
),
permissions = KnockRequestPermissions(
canAccept = false,
canDecline = false,
canBan = true,
),
),
aKnockRequestsListState(
knockRequests = AsyncData.Success(
persistentListOf(
aKnockRequestPresentable()
)
),
permissions = KnockRequestPermissions(
canAccept = true,
canDecline = true,
canBan = false,
),
),
)
}
fun aKnockRequestsListState(
knockRequests: AsyncData<ImmutableList<KnockRequestPresentable>> = AsyncData.Success(persistentListOf()),
currentAction: KnockRequestsAction = KnockRequestsAction.None,
asyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
permissions: KnockRequestPermissions = KnockRequestPermissions(
canAccept = true,
canDecline = true,
canBan = true,
),
eventSink: (KnockRequestsListEvents) -> Unit = {},
) = KnockRequestsListState(
knockRequests = knockRequests,
currentAction = currentAction,
asyncAction = asyncAction,
permissions = permissions,
eventSink = eventSink,
)

View File

@@ -0,0 +1,496 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.BigIcon
import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.AvatarType
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
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.TopAppBar
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
@Composable
fun KnockRequestsListView(
state: KnockRequestsListState,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
modifier = modifier,
topBar = {
KnockRequestsListTopBar(onBackClick = onBackClick)
},
content = { padding ->
KnockRequestsListContent(
state = state,
modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding),
)
}
)
}
@Composable
private fun KnockRequestsListContent(
state: KnockRequestsListState,
modifier: Modifier = Modifier,
) {
fun onAcceptClick(knockRequest: KnockRequestPresentable) {
state.eventSink(KnockRequestsListEvents.Accept(knockRequest))
}
fun onDeclineClick(knockRequest: KnockRequestPresentable) {
state.eventSink(KnockRequestsListEvents.Decline(knockRequest))
}
fun onBanClick(knockRequest: KnockRequestPresentable) {
state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequest))
}
var bottomPaddingInPixels by remember { mutableIntStateOf(0) }
Box(modifier.fillMaxSize()) {
when (state.knockRequests) {
is AsyncData.Success -> {
val knockRequests = state.knockRequests.data
if (knockRequests.isEmpty()) {
KnockRequestsEmptyList()
} else {
KnockRequestsList(
knockRequests = knockRequests,
canAccept = state.permissions.canAccept,
canDecline = state.permissions.canDecline,
canBan = state.permissions.canBan,
onAcceptClick = ::onAcceptClick,
onDeclineClick = ::onDeclineClick,
onBanClick = ::onBanClick,
contentPadding = PaddingValues(bottom = bottomPaddingInPixels.toDp()),
)
}
}
is AsyncData.Loading -> {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = spacedBy(16.dp),
modifier = Modifier.align(Alignment.Center)
) {
CircularProgressIndicator(color = ElementTheme.colors.iconPrimary)
Text(
text = stringResource(R.string.screen_knock_requests_list_initial_loading_title),
style = ElementTheme.typography.fontBodyLgRegular,
color = ElementTheme.colors.textPrimary,
)
}
}
else -> Unit
}
KnockRequestsActionsView(
currentAction = state.currentAction,
asyncAction = state.asyncAction,
onConfirm = {
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
},
onRetry = {
state.eventSink(KnockRequestsListEvents.RetryCurrentAction)
},
onDismiss = {
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
},
)
if (state.canAcceptAll) {
KnockRequestsAcceptAll(
onClick = {
state.eventSink(KnockRequestsListEvents.AcceptAll)
},
onHeightChange = { height ->
bottomPaddingInPixels = height
},
modifier = Modifier.align(Alignment.BottomCenter),
)
}
}
}
@Composable
private fun KnockRequestsActionsView(
currentAction: KnockRequestsAction,
asyncAction: AsyncAction<Unit>,
onConfirm: () -> Unit,
onDismiss: () -> Unit,
onRetry: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier) {
AsyncActionView(
async = asyncAction,
onSuccess = { onDismiss() },
onErrorDismiss = onDismiss,
confirmationDialog = {
KnockRequestActionConfirmation(
currentAction = currentAction,
onSubmit = onConfirm,
onDismiss = onDismiss,
)
},
progressDialog = {
KnockRequestActionProgress(target = currentAction)
},
errorMessage = {
when (currentAction) {
is KnockRequestsAction.Accept -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description)
is KnockRequestsAction.Decline -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description)
is KnockRequestsAction.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description)
KnockRequestsAction.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_failed_alert_description)
else -> ""
}
},
onRetry = onRetry,
)
}
}
@Composable
private fun KnockRequestActionConfirmation(
currentAction: KnockRequestsAction,
onSubmit: () -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {
val (title, content, submitText) = when (currentAction) {
KnockRequestsAction.AcceptAll -> Triple(
stringResource(R.string.screen_knock_requests_list_accept_all_alert_title),
stringResource(R.string.screen_knock_requests_list_accept_all_alert_description),
stringResource(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title),
)
is KnockRequestsAction.Decline -> Triple(
stringResource(R.string.screen_knock_requests_list_decline_alert_title),
stringResource(R.string.screen_knock_requests_list_decline_alert_description, currentAction.knockRequest.getBestName()),
stringResource(R.string.screen_knock_requests_list_decline_alert_confirm_button_title),
)
is KnockRequestsAction.DeclineAndBan -> Triple(
stringResource(R.string.screen_knock_requests_list_ban_alert_title),
stringResource(R.string.screen_knock_requests_list_ban_alert_description, currentAction.knockRequest.getBestName()),
stringResource(R.string.screen_knock_requests_list_ban_alert_confirm_button_title),
)
else -> return
}
ConfirmationDialog(
title = title,
content = content,
submitText = submitText,
onSubmitClick = onSubmit,
onDismiss = onDismiss,
modifier = modifier,
)
}
@Composable
private fun KnockRequestActionProgress(
target: KnockRequestsAction,
modifier: Modifier = Modifier,
) {
val progressText = when (target) {
is KnockRequestsAction.Accept -> stringResource(R.string.screen_knock_requests_list_accept_loading_title)
is KnockRequestsAction.Decline -> stringResource(R.string.screen_knock_requests_list_decline_loading_title)
is KnockRequestsAction.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_ban_loading_title)
KnockRequestsAction.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_loading_title)
else -> return
}
ProgressDialog(
text = progressText,
modifier = modifier,
)
}
@Composable
private fun KnockRequestsList(
knockRequests: ImmutableList<KnockRequestPresentable>,
canAccept: Boolean,
canDecline: Boolean,
canBan: Boolean,
onAcceptClick: (KnockRequestPresentable) -> Unit,
onDeclineClick: (KnockRequestPresentable) -> Unit,
onBanClick: (KnockRequestPresentable) -> Unit,
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(0.dp),
) {
LazyColumn(
modifier = modifier.fillMaxSize(),
contentPadding = contentPadding,
) {
itemsIndexed(knockRequests) { index, knockRequest ->
KnockRequestItem(
knockRequest = knockRequest,
onAcceptClick = onAcceptClick,
canBan = canBan,
canDecline = canDecline,
canAccept = canAccept,
onDeclineClick = onDeclineClick,
onBanClick = onBanClick,
)
if (index != knockRequests.size - 1) {
HorizontalDivider()
}
}
}
}
@Composable
private fun KnockRequestItem(
knockRequest: KnockRequestPresentable,
canAccept: Boolean,
canDecline: Boolean,
canBan: Boolean,
onAcceptClick: (KnockRequestPresentable) -> Unit,
onDeclineClick: (KnockRequestPresentable) -> Unit,
onBanClick: (KnockRequestPresentable) -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp)
) {
Avatar(
avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestItem),
avatarType = AvatarType.User,
)
Spacer(modifier = Modifier.width(16.dp))
Column {
// Name and date
Row {
Text(
modifier = Modifier
.clipToBounds()
.weight(1f),
text = knockRequest.getBestName(),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = ElementTheme.colors.textPrimary,
style = ElementTheme.typography.fontBodyLgMedium,
)
val formattedDate = knockRequest.formattedDate
if (!formattedDate.isNullOrEmpty()) {
Spacer(modifier = Modifier.width(8.dp))
Text(
text = formattedDate,
color = ElementTheme.colors.textSecondary,
style = ElementTheme.typography.fontBodySmRegular,
)
}
}
// UserId
if (!knockRequest.displayName.isNullOrEmpty()) {
Text(
text = knockRequest.userId.value,
color = ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = ElementTheme.typography.fontBodyMdRegular,
)
}
// Reason
val reason = knockRequest.reason
if (!reason.isNullOrBlank()) {
Spacer(modifier = Modifier.height(12.dp))
var isExpanded by rememberSaveable(knockRequest.userId) { mutableStateOf(false) }
var isExpandable by rememberSaveable(knockRequest.userId) { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.Top,
modifier = Modifier
.animateContentSize()
.clickable(enabled = isExpandable) { isExpanded = !isExpanded }
) {
Text(
text = reason,
style = ElementTheme.typography.fontBodyMdRegular,
maxLines = if (isExpanded) Int.MAX_VALUE else 3,
onTextLayout = { result ->
if (!isExpanded && result.hasVisualOverflow) {
isExpandable = true
}
},
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f),
)
Box(modifier = Modifier.size(24.dp)) {
if (isExpandable) {
Icon(
imageVector = if (isExpanded) CompoundIcons.ChevronUp() else CompoundIcons.ChevronDown(),
contentDescription = null,
tint = ElementTheme.colors.iconTertiary,
)
}
}
}
}
// Actions
if (canDecline || canAccept) {
Spacer(modifier = Modifier.height(12.dp))
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
if (canDecline) {
OutlinedButton(
text = stringResource(CommonStrings.action_decline),
onClick = {
onDeclineClick(knockRequest)
},
size = ButtonSize.MediumLowPadding,
modifier = Modifier.weight(1f),
)
}
if (canAccept) {
Button(
text = stringResource(CommonStrings.action_accept),
onClick = {
onAcceptClick(knockRequest)
},
size = ButtonSize.MediumLowPadding,
modifier = Modifier.weight(1f),
)
}
}
if (canBan) {
Spacer(modifier = Modifier.height(12.dp))
TextButton(
text = stringResource(R.string.screen_knock_requests_list_decline_and_ban_action_title),
onClick = {
onBanClick(knockRequest)
},
destructive = true,
size = ButtonSize.Small,
modifier = Modifier.fillMaxWidth(),
)
}
}
}
}
@Composable
private fun KnockRequestsAcceptAll(
onClick: () -> Unit,
onHeightChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
.shadow(elevation = 24.dp, spotColor = Color.Transparent)
.background(color = ElementTheme.colors.bgCanvasDefault)
.padding(vertical = 12.dp, horizontal = 16.dp)
.onSizeChanged { onHeightChange(it.height) }
) {
OutlinedButton(
text = stringResource(R.string.screen_knock_requests_list_accept_all_button_title),
onClick = onClick,
size = ButtonSize.Medium,
modifier = Modifier.fillMaxWidth(),
)
}
}
@Composable
private fun KnockRequestsEmptyList(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.padding(
horizontal = 32.dp,
vertical = 48.dp,
),
contentAlignment = Alignment.Center,
) {
IconTitleSubtitleMolecule(
title = stringResource(R.string.screen_knock_requests_list_empty_state_title),
subTitle = stringResource(R.string.screen_knock_requests_list_empty_state_description),
iconStyle = BigIcon.Style.Default(CompoundIcons.AskToJoin()),
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun KnockRequestsListTopBar(onBackClick: () -> Unit) {
TopAppBar(
titleStr = stringResource(R.string.screen_knock_requests_list_title),
navigationIcon = { BackButton(onClick = onBackClick) },
)
}
@PreviewsDayNight
@Composable
internal fun KnockRequestsListViewPreview(
@PreviewParameter(KnockRequestsListStateProvider::class) state: KnockRequestsListState
) = ElementPreview {
KnockRequestsListView(
state = state,
onBackClick = {},
)
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_single_knock_request_accept_button_title">"Прыняць"</string>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_single_knock_request_accept_button_title">"Приемане"</string>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ano, přijmout všechny"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Opravdu chcete přijmout všechny žádosti o vstup?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Přijmout všechny požadavky"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Přijmout vše"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nemohli jsme přijmout všechny žádosti. Chcete to zkusit znovu?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nepodařilo se přijmout všechny žádosti"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Přijímání všech žádostí o vstup"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Tuto žádost jsme nemohli přijmout. Chcete to zkusit znovu?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Žádost se nepodařilo přijmout"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Přijímání žádosti o vstup"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ano, odmítnout a vykázat"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Opravdu chcete odmítnout a vykázat %1$s? Tento uživatel nebude moci znovu požádat o vstup do této místnosti."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Odmítnout a zakázat vstup"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Odmítání vstupu a vykázání"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ano, odmítnout"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Opravdu chcete odmítnout %1$s žádost o vstup do této místnosti?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Odmítnout vstup"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Odmítnout a vykázat"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Tuto žádost jsme nemohli odmítnout. Chcete to zkusit znovu?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Žádost se nepodařilo odmítnout"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Odmítání žádosti o vstup"</string>
<string name="screen_knock_requests_list_empty_state_description">"Když někdo požádá o vstup do místnosti, uvidíte jeho žádost zde."</string>
<string name="screen_knock_requests_list_empty_state_title">"Žádná čekající žádost o vstup"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Načítání žádostí o vstup…"</string>
<string name="screen_knock_requests_list_title">"Žádosti o vstup"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d další chce vstoupit do této místnosti"</item>
<item quantity="few">"%1$s +%2$d další chtějí vstoupit do této místnosti"</item>
<item quantity="other">"%1$s +%2$d dalších chce vstoupit do této místnosti"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Zobrazit vše"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Přijmout"</string>
<string name="screen_room_single_knock_request_title">"%1$s chce vstoupit do této místnosti"</string>
<string name="screen_room_single_knock_request_view_button_title">"Zobrazit"</string>
</resources>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Iawn, derbyn y cyfan"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Ydych chi\'n siŵr eich bod am dderbyn pob cais i ymuno?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Derbyn pob cais"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Derbyn y cyfan"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Doedd dim modd derbyn pob cais. Hoffech chi roi cynnig arall arni?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Wedi methu derbyn pob cais"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Yn derbyn pob cais i ymuno"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Doedd dim modd derbyn y cais hwn. Hoffech chi roi cynnig arall arni?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Wedi methu â derbyn y cais"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Yn derbyn cais i ymuno"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Iawn, gwrthod a gwahardd"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Ydych chi\'n siŵr eich bod am wrthod a gwahardd %1$s? Bydd y defnyddiwr hwn ddim yn gallu gofyn am fynediad i ymuno â\'r ystafell hon eto."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Gwrthod a gwahardd mynediad"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Yn gwrthod a gwahardd mynediad"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Iawn, gwrthod"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Ydych chi\'n siŵr eich bod am wrthod %1$s cais i ymuno â\'r ystafell hon?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Gwrthod mynediad"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Gwrthod a gwahardd"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Doedd dim modd i ni wrthod y cais hwn. Hoffech chi roi cynnig arall arni?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Wedi methu â gwrthod y cais"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Yn gwrthod cais i ymuno"</string>
<string name="screen_knock_requests_list_empty_state_description">"Pan fydd rhywun yn gofyn am gael ymuno â\'r ystafell, byddwch yn gallu gweld eu cais yma."</string>
<string name="screen_knock_requests_list_empty_state_title">"Dim cais i ymuno yn disgwyl"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Yn llwytho ceisiadau i ymuno…"</string>
<string name="screen_knock_requests_list_title">"Ceisiadau i ymuno"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="zero">"Dyw %1$s na +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
<item quantity="one">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
<item quantity="two">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
<item quantity="few">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
<item quantity="many">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
<item quantity="other">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Gweld y cyfan"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Derbyn"</string>
<string name="screen_room_single_knock_request_title">"Mae %1$s eisiau ymuno â\'r ystafell hon"</string>
<string name="screen_room_single_knock_request_view_button_title">"Golwg"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, acceptér alle"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Er du sikker på, at du vil acceptere alle anmodninger om at deltage?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Acceptér alle anmodninger"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Acceptér alle"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Vi kunne ikke acceptere alle anmodninger. Vil du prøve igen?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kunne ikke acceptere alle anmodninger"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepterer alle anmodninger om at deltage"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Vi kunne ikke acceptere denne anmodning. Vil du prøve igen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Kunne ikke acceptere anmodningen"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Accepterer anmodning om at deltage"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, afvis og spær"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Er du sikker på, at du vil afvise og spærre %1$s? Denne bruger vil ikke kunne anmode om adgang til at deltage i dette rum igen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Afvis og spær for deres adgang til rummet"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Afviser og spærrer for adgang"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, afvis"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Er du sikker på, at du vil afvise %1$ss anmodning om at deltage i dette rum?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Afvis adgang"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Afvis og spær"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunne ikke afvise denne anmodning. Vil du prøve igen?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Anmodningen kunne ikke afvises"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Afviser anmodning om at deltage"</string>
<string name="screen_knock_requests_list_empty_state_description">"Når nogen beder om at deltage i rummet, kan du se deres anmodning her."</string>
<string name="screen_knock_requests_list_empty_state_title">"Ingen ventende anmodning om at deltage"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Indlæser anmodninger om at deltage…"</string>
<string name="screen_knock_requests_list_title">"Anmodninger om at deltage"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s + %2$d anden ønsker at deltage i dette rum"</item>
<item quantity="other">"%1$s + %2$d andre ønsker at deltage i dette rum"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Se alle"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Accepter"</string>
<string name="screen_room_single_knock_request_title">"%1$s ønsker at deltage i dette rum"</string>
<string name="screen_room_single_knock_request_view_button_title">"Vis"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, akzeptiere alle"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Bist du sicher, dass du alle Beitrittsanfragen akzeptieren möchtest?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Akzeptiere alle Beitrittsanfragen"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Alle akzeptieren"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Wir konnten nicht alle Beitrittsanfragen annehmen. Möchtest du es noch mal versuchen?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Es konnten nicht alle Beitrittsanfragen akzeptiert werden"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Alle Beitrittsanfragen werden angenommen"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Wir konnten diese Beitrittsanfrage nicht annehmen. Möchtest du es noch mal versuchen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Die Beitrittsanfrage konnte nicht akzeptiert werden"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Beitrittsanfrage annehmen"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, ablehnen und sperren"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Bist du sicher, dass du %1$s ablehnen und sperren möchtest? Dieser Nutzer kann dann keinen erneuten Beitritt zu diesem Chat anfragen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Ablehnen und Zugriff sperren"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Ablehnung und Sperrung des Zugriffs"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, ablehnen"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Bist du sicher, dass du die Beitrittsanfrage von %1$s zu diesem Chat ablehnen möchtest?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Zugriff ablehnen"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Ablehnen und sperren"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Wir konnten diese Beitrittsanfrage nicht ablehnen. Möchtest du es noch mal versuchen?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Beitrittsanfrage konnte nicht abgelehnt werden"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Ablehnung der Beitrittsanfrage"</string>
<string name="screen_knock_requests_list_empty_state_description">"Sollte jemand um Beitritt zum Chat bitten, kannst du die Anfrage hier sehen."</string>
<string name="screen_knock_requests_list_empty_state_title">"Keine ausstehende Beitrittsanfrage"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Beitrittsanfragen werden geladen …"</string>
<string name="screen_knock_requests_list_title">"Beitrittsanfragen"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d weiterer möchten diesem Chat beitreten"</item>
<item quantity="other">"%1$s +%2$d weitere möchten diesem Chat beitreten"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Alles ansehen"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Akzeptieren"</string>
<string name="screen_room_single_knock_request_title">"%1$s möchte diesem Chat beitreten"</string>
<string name="screen_room_single_knock_request_view_button_title">"Ansicht"</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ναι, αποδοχή όλων"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Σίγουρα θες να αποδεχτείς όλα τα αιτήματα συμμετοχής;"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Αποδοχή όλων των αιτημάτων"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Αποδοχή όλων"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Δεν μπορέσαμε να δεχτούμε όλα τα αιτήματα. Θες να προσπαθήσεις ξανά;"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Αποτυχία αποδοχής όλων των αιτημάτων"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Αποδοχή όλων των αιτημάτων συμμετοχής"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Δεν μπορέσαμε να δεχτούμε αυτό το αίτημα. Θα θέλατε να προσπαθήσετε ξανά;"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Αποτυχία αποδοχής αιτήματος"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Γίνεται αποδοχή αιτήματος συμμετοχής"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ναι, απόρριψη και αποκλεισμός"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Είστε σίγουροι ότι θέλετε να απορρίψετε και να αποκλείσετε το χρήστη %1$s; Αυτός ο χρήστης δεν θα μπορέσει να ζητήσει ξανά πρόσβαση για να συμμετάσχει σε αυτή την αίθουσα."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Απόρριψη και αποκλεισμός πρόσβασης"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Γίνεται απόρριψη και αποκλεισμός πρόσβασης"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ναι, απόρριψη"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Είστε σίγουροι ότι θέλετε να απορρίψετε το αίτημα του %1$s να συμμετάσχετε σε αυτήν την αίθουσα;"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Απόρριψη πρόσβασης"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Απόρριψη και αποκλεισμός"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Δεν μπορέσαμε να απορρίψουμε αυτό το αίτημα. Θα θέλατε να προσπαθήσετε ξανά;"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Απέτυχε η απόρριψη του αιτήματος"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Γίνεται απόρριψη αιτήματος συμμετοχής"</string>
<string name="screen_knock_requests_list_empty_state_description">"Όταν κάποιος θα ζητήσει να συμμετάσχει στην αίθουσα, θα μπορείτε να δείτε το αίτημά του εδώ."</string>
<string name="screen_knock_requests_list_empty_state_title">"Δεν υπάρχει εκκρεμές αίτημα συμμετοχής"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Φόρτωση αιτημάτων συμμετοχής…"</string>
<string name="screen_knock_requests_list_title">"Αιτήματα συμμετοχής"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d άλλοι θέλουν να συμμετάσχουν σε αυτή την αίθουσα"</item>
<item quantity="other">"%1$s +%2$d άλλοι θέλουν να συμμετάσχουν σε αυτή την αίθουσα"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Προβολή όλων"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Αποδοχή"</string>
<string name="screen_room_single_knock_request_title">"%1$s θέλει να συμμετάσχει σε αυτή την αίθουσα"</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sí, aceptar todas"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"¿Estás seguro de que quieres aceptar todas las solicitudes de unión?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Aceptar todas las solicitudes"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Aceptar todas"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"No pudimos aceptar todas las solicitudes. ¿Quieres volver a intentarlo?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"No se pudieron aceptar todas las solicitudes"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Aceptando todas las solicitudes de unión"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"No pudimos aceptar esta solicitud. ¿Quieres volver a intentarlo?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"No se pudo aceptar la solicitud"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Aceptando solicitud de unión"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sí, rechazar y vetar"</string>
<string name="screen_knock_requests_list_ban_alert_description">"¿Estás seguro de que quieres rechazar y vetar a %1$s? Este usuario no podrá volver a solicitar acceso para unirse a esta sala."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Rechazar y vetar el acceso"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Rechazando y vetando acceso"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sí, rechazar"</string>
<string name="screen_knock_requests_list_decline_alert_description">"¿Estás seguro de que deseas rechazar la solicitud de %1$s para unirse a esta sala?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Rechazar el acceso"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Rechazar y vetar"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"No pudimos rechazar esta solicitud. ¿Quieres volver a intentarlo?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"No se pudo rechazar la solicitud"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Rechazando solicitud de unión"</string>
<string name="screen_knock_requests_list_empty_state_description">"Cuando alguien pida unirse a la sala, podrás ver su solicitud aquí."</string>
<string name="screen_knock_requests_list_empty_state_title">"No hay ninguna solicitud de unión pendiente"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Cargando solicitudes de unión…"</string>
<string name="screen_knock_requests_list_title">"Solicitudes de unión"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s y %2$d más quieren unirse a esta sala"</item>
<item quantity="other">"%1$s y otros %2$d más quieren unirse a esta sala"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ver todo"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Aceptar"</string>
<string name="screen_room_single_knock_request_title">"%1$s quiere unirse a esta sala"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Jah, võta kõik vastu"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Kas sa oled kindel, et soovid kõik vastu liitumist soovinud võtta?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Võta kõik vastu"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Nõustu kõigiga"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Kõikide päringutega nõustumine polnud võimalik. Kas sa sooviksid uuesti proovida?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kõikide liitumispalvetega nõustumine ei õnnestunud"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Nõustume kõikide liitumispalvetega"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Selle liitumispalvega nõustumine ei õnnestunud. Kas sa sooviksid uuesti proovida?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Liitumispalvega nõustumine ei õnnestunud"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Nõustume liitumispalvega"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Jah, keeldu liitumisest ning keela ligipääs"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa ning seada talle suhtluskeelu? Seetõttu ta ei saa ka enam hiljem liitumispalvet saata."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Keeldu liitumisest ja keela ligipääs"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Keeldume liitumispalvest ja seame suhtluskeelu"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Jah, keeldu"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Keela ligipääs"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Keeldu ja määra suhtluskeeld"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Selle liitumispalvest tagasilükkamine ei õnnestunud. Kas sa sooviksid uuesti proovida?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Liitumispalvest tagasilükkamine ei õnnestunud"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Lükkame liitumispalve tagasi"</string>
<string name="screen_knock_requests_list_empty_state_description">"Kui keegi soovib jututoaga liituda, siis need päringud on kuvatud siin."</string>
<string name="screen_knock_requests_list_empty_state_title">"Pole ühtegi liitumispalvet"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Laadime liitumispalveid…"</string>
<string name="screen_knock_requests_list_title">"Liitumispalved"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s + veel %2$d kasutaja soovivad selle jututoaga liituda"</item>
<item quantity="other">"%1$s + veel %2$d kasutajat soovivad selle jututoaga liituda"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Vaata kõiki"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Nõustu"</string>
<string name="screen_room_single_knock_request_title">"%1$s soovib selle jututoaga liituda"</string>
<string name="screen_room_single_knock_request_view_button_title">"Vaata"</string>
</resources>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Bai, onartu guztiak"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Onartu eskaera guztiak"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Onartu guztiak"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Bai, ukatu eta ezarri debekua"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Bai, ukatu"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Ukatu sarbidea"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Ukatu eta ezarri debekua"</string>
<string name="screen_knock_requests_list_title">"Sartzeko eskaerak"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s + beste %2$d gelara batu nahi dute"</item>
<item quantity="other">"%1$s + beste %2$d gelara batu nahi dute"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ikusi guztiak"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Onartu"</string>
<string name="screen_room_single_knock_request_title">"%1$s(e)k gela honetara sartu nahi du"</string>
<string name="screen_room_single_knock_request_view_button_title">"Ikusi"</string>
</resources>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"بله. پذیرش همه"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"پذیرش همهٔ درخواست‌ها"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"پذیرش همه"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"شکست در پذیرش همهٔ درخواست‌ها"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"پذیرفتن همهٔ درخواست‌های پیوستن"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"شکست در پذیرش درخواست"</string>
<string name="screen_knock_requests_list_accept_loading_title">"پذیرفتن درخواست پیوستن"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"بله. رد و انسداد"</string>
<string name="screen_knock_requests_list_ban_alert_title">"رد و تحریم دسترسی"</string>
<string name="screen_knock_requests_list_ban_loading_title">"رد کردن و تحریم دسترسی"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"بله. رد شود"</string>
<string name="screen_knock_requests_list_decline_alert_title">"رد کردن دسترسی"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"رد و تحریم"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"شکتس در رد درخواست"</string>
<string name="screen_knock_requests_list_decline_loading_title">"در کردن درخواست پیوستن"</string>
<string name="screen_knock_requests_list_title">"درخواست‌های پیوستن"</string>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"دیدن همه"</string>
<string name="screen_room_single_knock_request_accept_button_title">"پذیرش"</string>
<string name="screen_room_single_knock_request_title">"%1$s می‌خواهد به این اتاق بپیوندد"</string>
<string name="screen_room_single_knock_request_view_button_title">"نما"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Kyllä, hyväksy kaikki"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Haluatko varmasti hyväksyä kaikki liittymispyynnöt?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Hyväksy kaikki pyynnöt"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Hyväksy kaikki"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Emme voineet hyväksyä kaikkia pyyntöjä. Haluaisitko yrittää uudelleen?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kaikkien pyyntöjen hyväksyminen epäonnistui"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Hyväksytään kaikkia liittymispyyntöjä"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Emme voineet hyväksyä tätä pyyntöä. Haluaisitko yrittää uudelleen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Pyynnön hyväksyminen epäonnistui"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Hyväksytään liittymispyyntöä"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Kyllä, hylkää ja anna porttikielto"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Haluatko varmasti hylätä käyttäjän %1$s pyynnön liittyä huoneeseen ja antaa hänelle porttikiellon? Hän ei voi enää pyytää lupaa liittyä tähän huoneeseen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Hylkää ja anna porttikielto"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Hylätään pyyntöä ja annetaan porttikieltoa"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Kyllä, hylkää"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Haluatko varmasti hylätä käyttäjän %1$s pyynnön liittyä tähän huoneeseen?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Hylkää pyyntö"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Hylkää ja anna porttikielto"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Emme voineet hylätä tätä pyyntöä. Haluaisitko yrittää uudelleen?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Pyynnön hylkääminen epäonnistui"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Hylätään liittymispyyntöä"</string>
<string name="screen_knock_requests_list_empty_state_description">"Kun joku pyytää liittyä huoneeseen, näet hänen pyyntönsä täällä."</string>
<string name="screen_knock_requests_list_empty_state_title">"Ei odottavia liittymispyyntöjä"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Ladataan liittymispyyntöjä…"</string>
<string name="screen_knock_requests_list_title">"Liittymispyynnöt"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d muu haluavat liittyä tähän huoneeseen"</item>
<item quantity="other">"%1$s +%2$d muuta haluavat liittyä tähän huoneeseen"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Näytä kaikki"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Hyväksy"</string>
<string name="screen_room_single_knock_request_title">"%1$s haluaa liittyä tähän huoneeseen"</string>
<string name="screen_room_single_knock_request_view_button_title">"Näytä"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Oui, tout accepter"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Êtes-vous sûr de vouloir accepter toutes les demandes pour rejoindre le salon ?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Tout accepter"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Tout accepter"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Toutes les demandes nont pas pu être acceptées. Voulez-vous réessayer ?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Toutes les demandes nont pas été acceptées"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepter toutes les demandes à rejoindre"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"La demande na pas pu être acceptée. Voulez-vous réessayer ?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Impossible daccepter la demande"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Accepter la demande à rejoindre"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Oui, rejeter et bannir"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Êtes-vous sûr de vouloir rejeter la demande et bannir %1$s ? Cet utilisateur ne pourra pas demander à nouveau à rejoindre ce salon."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Refuser et interdire laccès"</string>
<string name="screen_knock_requests_list_ban_loading_title">"En cours de traitement…"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Oui, refuser"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Êtes-vous sûr de vouloir refuser la demande de %1$s à rejoindre le salon ?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Refuser laccès"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Refuser et bannir"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nous navons pas pu refuser cette demande. Voulez-vous réessayer ?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Echec"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Traitement en cours…"</string>
<string name="screen_knock_requests_list_empty_state_description">"Lorsque quelquun demandera à rejoindre le salon, vous pourrez voir sa demande ici."</string>
<string name="screen_knock_requests_list_empty_state_title">"Personne ne demande à rejoindre le salon"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Chargement…"</string>
<string name="screen_knock_requests_list_title">"Demandes en attente"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s et %2$d autre personne souhaitent rejoindre ce salon"</item>
<item quantity="other">"%1$s et %2$d autres personnes souhaitent rejoindre ce salon"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Tout afficher"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Accepter"</string>
<string name="screen_room_single_knock_request_title">"%1$s souhaite rejoindre ce salon"</string>
<string name="screen_room_single_knock_request_view_button_title">"Voir"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Igen, az összes elfogadása"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Biztos, hogy elfogadja az összes csatlakozási kérelmet?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Minden kérés elfogadása"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Összes elfogadása"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nem sikerült az összes kérés fogadása. Újra megpróbálja?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nem sikerült az összes kérés elfogadása"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Összes csatlakozási kérés elfogadása"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Nem sikerült elfogadni a kérést. Megpróbálja újra?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Nem sikerült elfogadni a kérést"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Csatlakozási kérés elfogadása"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Igen, elutasítás és kitiltás"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Biztos, hogy elutasítja %1$s kérését és ki is tiltja? Többé nem fogja tudni azt kérni, hogy csatlakozhasson ehhez a szobához."</string>
<string name="screen_knock_requests_list_ban_alert_title">"A hozzáférés elutasítása és kitiltás"</string>
<string name="screen_knock_requests_list_ban_loading_title">"A hozzáférés megtagadása és kitiltás"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Igen, elutasítás"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Biztos, hogy elutasítja %1$s kérését, hogy csatlakozzon a szobához?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Hozzáférés elutasítása"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Elutasítás és kitiltás"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nem sikerült elutasítani a kérést. Megpróbálja újra?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Nem sikerült elutasítani a kérést"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Csatlakozási kérés elutasítása"</string>
<string name="screen_knock_requests_list_empty_state_description">"Ha valaki csatlakozni kíván a szobához, itt láthatja a kérését."</string>
<string name="screen_knock_requests_list_empty_state_title">"Nincs függőben lévő csatlakozási kérelem"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Csatlakozási kérések betöltése…"</string>
<string name="screen_knock_requests_list_title">"Csatlakozási kérelem"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához"</item>
<item quantity="other">"%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Összes megtekintése"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Elfogadás"</string>
<string name="screen_room_single_knock_request_title">"%1$s szeretne csatlakozni ehhez a szobához"</string>
<string name="screen_room_single_knock_request_view_button_title">"Megtekintés"</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ya, terima semua"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Apakah Anda yakin ingin menerima semua permintaan untuk bergabung?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Terima semua permintaan"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Terima semua"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Kami tidak dapat menerima semua permintaan. Apakah Anda ingin mencoba lagi?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Gagal menerima semua permintaan"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Menerima semua permintaan untuk bergabung"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Kami tidak dapat menerima permintaan ini. Apakah Anda ingin mencoba lagi?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Gagal menerima permintaan"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Menerima permintaan untuk bergabung"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ya, tolak dan cekal"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Apakah Anda yakin ingin menolak dan mencekal %1$s? Pengguna ini tidak akan dapat meminta akses untuk bergabung ke ruangan ini lagi."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Tolak dan cekal akses"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Menolak dan mencekal akses"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ya, tolak"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Apakah Anda yakin ingin menolak permintaan %1$s untuk bergabung ke ruangan ini?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Tolak akses"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Tolak dan cekal"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Kami tidak dapat menolak permintaan ini. Apakah Anda ingin mencoba lagi?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Gagal menolak permintaan"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Menolak permintaan untuk bergabung"</string>
<string name="screen_knock_requests_list_empty_state_description">"Ketika seseorang akan meminta untuk bergabung dengan ruangan, Anda akan dapat melihat permintaan mereka di sini."</string>
<string name="screen_knock_requests_list_empty_state_title">"Tidak ada permintaan yang tertunda untuk bergabung"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Memuat permintaan untuk bergabung…"</string>
<string name="screen_knock_requests_list_title">"Permintaan untuk bergabung"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="other">"%1$s +%2$d lainnya ingin bergabung ke ruangan ini"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Lihat semua"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Terima"</string>
<string name="screen_room_single_knock_request_title">"%1$s ingin bergabung ke ruangan ini"</string>
<string name="screen_room_single_knock_request_view_button_title">"Lihat"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sì, accetta tutte"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Sei sicuro di voler accettare tutte le richieste di accesso?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Accetta tutte le richieste"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Accetta tutte"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Non siamo riusciti ad accettare tutte le richieste. Vuoi riprovare?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Non è stato possibile accettare tutte le richieste"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Accettazione di tutte le richieste di ingresso"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Non è stato possibile accettare questa richiesta. Vuoi riprovare?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Impossibile accettare la richiesta"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Accettazione della richiesta di ingresso"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sì, rifiuta e blocca"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Sei sicuro di voler rifiutare e bloccare %1$s? Questo utente non potrà richiedere nuovamente l\'accesso per entrare in questa stanza."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Rifiuta e blocca l\'accesso"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Rifiuto e divieto di accesso"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sì, rifiuta"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Sei sicuro di voler rifiutare la richiesta di %1$s ad entrare in a questa stanza?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Rifiuta l\'accesso"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Rifiuta e blocca"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Non è stato possibile rifiutare questa richiesta. Vuoi riprovare?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Non è stato possibile rifiutare la richiesta"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Rifiuto della richiesta di ingresso"</string>
<string name="screen_knock_requests_list_empty_state_description">"Quando qualcuno ti chiederà di entrare nella stanza, potrai vedere la sua richiesta qui."</string>
<string name="screen_knock_requests_list_empty_state_title">"Nessuna richiesta di accesso in sospeso"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Caricamento richieste di partecipazione…"</string>
<string name="screen_knock_requests_list_title">"Richieste di accesso"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d vogliono entrare in questa stanza"</item>
<item quantity="other">"%1$s +%2$d vogliono entrare in questa stanza"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Visualizza tutte"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Accetta"</string>
<string name="screen_room_single_knock_request_title">"%1$s vuole entrare in questa stanza"</string>
<string name="screen_room_single_knock_request_view_button_title">"Visualizza"</string>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_single_knock_request_accept_button_title">"მიღება"</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"네, 모두 수락합니다"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"모든 가입 요청을 정말로 수락하시겠습니까?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"모든 요청 수락"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"모두 수락"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"모든 요청을 처리할 수 없습니다. 다시 시도하시겠습니까?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"모든 요청을 수락하지 못했습니다."</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"모든 가입 요청 수락"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"이 요청을 수락할 수 없습니다. 다시 시도하시겠습니까?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"요청을 수락하지 못했습니다"</string>
<string name="screen_knock_requests_list_accept_loading_title">"가입 요청 수락"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"네, 거절하고 차단합니다"</string>
<string name="screen_knock_requests_list_ban_alert_description">"%1$s 을 거부하고 차단하시겠습니까? 이 사용자는 이 방에 다시 참여하기 위해 액세스를 요청할 수 없습니다."</string>
<string name="screen_knock_requests_list_ban_alert_title">"접근 거부 및 차단"</string>
<string name="screen_knock_requests_list_ban_loading_title">"접근 거부 및 차단"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"네, 거절합니다"</string>
<string name="screen_knock_requests_list_decline_alert_description">"%1$s 의 이 방에 대한 요청을 정말 거부하시겠습니까?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"접근 거부"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"거부 및 차단"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"이 요청을 거부할 수 없습니다. 다시 시도하시겠습니까?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"요청 거부에 실패했습니다"</string>
<string name="screen_knock_requests_list_decline_loading_title">"가입 요청 거부"</string>
<string name="screen_knock_requests_list_empty_state_description">"누군가가 방에 참여 요청을 한다면, 여기에서 그 요청을 볼 수 있습니다."</string>
<string name="screen_knock_requests_list_empty_state_title">"보류 중인 가입 요청이 없습니다."</string>
<string name="screen_knock_requests_list_initial_loading_title">"가입 요청을 로딩 중…"</string>
<string name="screen_knock_requests_list_title">"참여 요청"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="other">"%1$s +%2$d 명이 이 방에 참여하고 싶어합니다."</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"모두 보기"</string>
<string name="screen_room_single_knock_request_accept_button_title">"수락"</string>
<string name="screen_room_single_knock_request_title">"%1$s 이 방에 참여하고 싶습니다."</string>
<string name="screen_room_single_knock_request_view_button_title">"보기"</string>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_single_knock_request_accept_button_title">"Priimti"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, godta alle"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Er du sikker på at du vil godta alle forespørsler om å bli med?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Godta alle forespørsler"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Godta alle"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Vi kunne ikke godta alle forespørsler. Vil du prøve igjen?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kunne ikke godta alle forespørsler"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Godtar alle forespørsler om å bli med"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Vi kunne ikke godta denne forespørselen. Vil du prøve igjen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Kunne ikke godta forespørselen"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Godtar forespørsel om å bli med"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, avslå og utesteng"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Er du sikker på at du vil avvise og utestenge %1$s? Denne brukeren vil ikke kunne be om tilgang til dette rommet igjen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Avslå og forby tilgang"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Avslår og forbyr tilgang"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, avslå"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Er du sikker på at du vil avslå %1$ss forespørsel om å bli med i dette rommet?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Avslå tilgang"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Avslå og forby"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunne ikke avslå denne forespørselen. Vil du prøve igjen?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Kunne ikke avslå forespørselen"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Avslår forespørsel om å bli med"</string>
<string name="screen_knock_requests_list_empty_state_description">"Når noen ber om å bli med i rommet, vil du kunne se forespørselen deres her."</string>
<string name="screen_knock_requests_list_empty_state_title">"Ingen ventende forespørsel om å bli med"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Laster inn forespørsler om å bli med…"</string>
<string name="screen_knock_requests_list_title">"Forespørsler om å bli med"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d andre ønsker å bli med i dette rommet"</item>
<item quantity="other">"%1$s +%2$d andre ønsker å bli med i dette rommet"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Vis alle"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Godta"</string>
<string name="screen_room_single_knock_request_title">"%1$s ønsker å bli med i dette rommet"</string>
<string name="screen_room_single_knock_request_view_button_title">"Vis"</string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_single_knock_request_accept_button_title">"Accepteren"</string>
<string name="screen_room_single_knock_request_view_button_title">"Bekijken"</string>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Tak, akceptuj wszystkie"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Czy na pewno chcesz zaakceptować wszystkie prośby o dołączenie?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Akceptuj wszystkie prośby"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Akceptuj wszystkie"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nie udało się zaakceptować wszystkich próśb. Czy chcesz spróbować ponownie?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nie udało się zaakceptować wszystkich próśb"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Akceptowanie wszystkich próśb o dołączenie"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Nie udało się zaakceptować prośby. Czy chcesz spróbować ponownie?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Nie udało się zaakceptować prośby"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Akceptuję prośbę o dołączenie"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Tak, odrzuć i zbanuj"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Czy na pewno chcesz odrzucić i zbanować %1$s? Użytkownik nie będzie mógł ponownie poprosić o dostęp do tego pokoju."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Odrzuć i zbanuj dostęp"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Odrzucanie i banowanie dostępu"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Tak, odrzuć"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Czy na pewno chcesz odrzucić %1$s prośbę o dołączenie do tego pokoju?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Odrzuć dostęp"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Odrzuć i zbanuj"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nie udało się odrzucić prośby. Czy chcesz spróbować ponownie?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Nie udało się odrzucić prośby"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Odrzucanie prośby o dołączenie"</string>
<string name="screen_knock_requests_list_empty_state_description">"Kiedy ktoś poprosi o dołączenie do pokoju, będziesz mógł zobaczyć jego prośbę tutaj."</string>
<string name="screen_knock_requests_list_empty_state_title">"Brak oczekujących próśb o dołączenie"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Wczytywanie próśb o dołączenie…"</string>
<string name="screen_knock_requests_list_title">"Prośby o dołączenie"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d inny chce dołączyć do pokoju"</item>
<item quantity="few">"%1$s +%2$d innych chce dołączyć do pokoju"</item>
<item quantity="many">"%1$s +%2$d innych chce dołączyć do pokoju"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Wyświetl wszystkie"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Akceptuj"</string>
<string name="screen_room_single_knock_request_title">"%1$s chce dołączyć do tego pokoju"</string>
<string name="screen_room_single_knock_request_view_button_title">"Wyświetl"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sim, aceitar todos"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Tem certeza de que deseja aceitar todos os pedidos de entrada?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Aceitar todos os pedidos"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Aceitar todos"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Não pudemos aceitar todas as solicitações. Você gostaria de tentar novamente?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Falha ao aceitar todas as solicitações"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Aceitando todas as solicitações de entrada"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Não pudemos aceitar essa solicitação. Você gostaria de tentar novamente?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Falha ao aceitar a solicitação"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Aceitando solicitação de entrada"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sim, recusar e banir"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Você tem certeza de que deseja recusar e banir %1$s? Este usuário não poderá solicitar acesso para entrar nesta sala novamente."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Recusar e proibir o acesso"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Recusando e proibindo o acesso"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sim, recusar"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Você tem certeza de que deseja recusar a solicitação de %1$s para entrar nesta sala?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Recusar acesso"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Recusar e banir"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Não foi possível recusar esta solicitação. Você gostaria de tentar novamente?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Falha ao recusar a solicitação"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Recusando a solicitação de entrada"</string>
<string name="screen_knock_requests_list_empty_state_description">"Quando alguém pedir para entrar na sala, você poderá ver o pedido aqui."</string>
<string name="screen_knock_requests_list_empty_state_title">"Nenhum pedido de entrada pendente"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Carregando solicitações de entrada…"</string>
<string name="screen_knock_requests_list_title">"Pedidos de entrada"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s + outro %2$d desejam entrar nesta sala"</item>
<item quantity="other">"%1$s + outros %2$d desejam entrar nesta sala"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ver tudo"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Aceitar"</string>
<string name="screen_room_single_knock_request_title">"%1$s quer entrar nesta sala"</string>
<string name="screen_room_single_knock_request_view_button_title">"Visualizar"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sim, aceitar todos"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Tens a certeza de que queres aceitar todos os pedidos de entrada?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Aceitar todos os pedidos"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Aceitar todos"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Não foi possível aceitar todos os pedidos. Queres tentar novamente?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Falha ao aceitar todos os pedidos"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"A aceitar todos os pedidos de entrada"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Não foi possível aceitar este pedido. Queres tentar novamente?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Falha ao aceitar pedido"</string>
<string name="screen_knock_requests_list_accept_loading_title">"A aceitar pedido de entrada"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sim, recusar e proibir"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Tens a certeza de que queres recusar e banir %1$s? Este utilizador não poderá voltar a pedir para entrar nesta sala."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Recusar e banir"</string>
<string name="screen_knock_requests_list_ban_loading_title">"A rejeitar pedido e a banir o utilizador"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sim, rejeitar"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Tens a certeza que queres recusar o pedido de entrada de %1$s?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Rejeitar entrada"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Recusar e banir"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Não foi possível rejeitar este pedido. Queres tentar novamente?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Falha ao rejeitar pedido"</string>
<string name="screen_knock_requests_list_decline_loading_title">"A rejeitar pedido de entrada"</string>
<string name="screen_knock_requests_list_empty_state_description">"Quando alguém pedir para entrar na sala, irás poder rever o pedido aqui."</string>
<string name="screen_knock_requests_list_empty_state_title">"Sem pedidos de entrada"</string>
<string name="screen_knock_requests_list_initial_loading_title">"A carregar pedidos de entrada…"</string>
<string name="screen_knock_requests_list_title">"Pedidos de entrada"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d outro querem entrar nesta sala"</item>
<item quantity="other">"%1$s +%2$d outros querem entrar nesta sala"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ver todos"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Aceitar"</string>
<string name="screen_room_single_knock_request_title">"%1$s quer entrar nesta sala"</string>
<string name="screen_room_single_knock_request_view_button_title">"Ver"</string>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Da, acceptati tot"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Sunteți sigur că doriți să acceptați toate cererile de alăturare?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Acceptați toate cererile"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Acceptați tot"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nu am putut accepta toate cererile. Doriți să încercați din nou?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nu s-au putut accepta toate cererile"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Se acceptă toate cererile de alăturare"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Nu am putut accepta această cerere. Doriți să încercați din nou?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Nu s-a putut accepta cererea"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Se acceptă cererea de alăturare"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Da, refuzați și interziceți"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Sunteți sigur că doriți să refuzați și să interziceți accesul lui %1$s? Acest utilizator nu va mai putea cere accesul pentru a se alătura acestei camere."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Refuzați și interziceți accesul"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Se refuză și interzice accesul"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Da, refuzați"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Sunteți sigur că doriți să refuzați cererea %1$s de a vă alătura acestei camere?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Refuzați accesul"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Refuzați și interziceți"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nu am putut refuza această cerere. Doriți să încercați din nou?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Cererea nu a putut fi respinsă"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Se refuza cererea de alăturare"</string>
<string name="screen_knock_requests_list_empty_state_description">"Când cineva va cere să se alăture camerei, veți putea vedea cererea aici."</string>
<string name="screen_knock_requests_list_empty_state_title">"Nu există cereri de alăturare în așteptare"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Se încarcă cererile de alăturare…"</string>
<string name="screen_knock_requests_list_title">"Cereri de alăturare"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d utilizator doresc să se alăture acestei camere"</item>
<item quantity="few">"%1$s +%2$d utilizatori doresc să se alăture acestei camere"</item>
<item quantity="other">"%1$s +%2$d utilizatori doresc să se alăture acestei camere"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Vizualizați tot"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Acceptați"</string>
<string name="screen_room_single_knock_request_title">"%1$s dorește să se alăture acestei camere"</string>
<string name="screen_room_single_knock_request_view_button_title">"Vizualizați"</string>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Да, принять все"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Вы действительно хотите принять все заявки на присоединение?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Принять все запросы"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Принять всё"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Мы не смогли принять все запросы. Хотите попробовать еще раз?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Не удалось принять все запросы"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Принять все заявки на присоединение"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Мы не смогли принять этот запрос. Хотите попробовать еще раз?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Не удалось принять запрос"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Принятие заявки на присоединение"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Да, отклонить и запретить"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Вы уверены, что хотите отклонить и запретить %1$s? Этот пользователь больше не сможет запросить доступ к этой комнате."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Отклонить и запретить доступ"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Отклонение и запрет доступа"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Да, отклонить"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Вы уверены, что хотите отклонить %1$s запрос на присоединение к этой комнате?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Отклонить доступ"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Отклонить и запретить"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Мы не смогли отклонить этот запрос. Хотите попробовать еще раз?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Не удалось отклонить запрос"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Отклонение заявки на присоединение"</string>
<string name="screen_knock_requests_list_empty_state_description">"Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате."</string>
<string name="screen_knock_requests_list_empty_state_title">"Нет ожидающих запросов на присоединение"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Загрузка запросов на присоединение…"</string>
<string name="screen_knock_requests_list_title">"Запросы на вступление"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d хочет присоединиться к этой комнате"</item>
<item quantity="few">"%1$s +%2$d хотят присоединиться к этой комнате"</item>
<item quantity="many">"%1$s +%2$d хотят присоединиться к этой комнате"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Показать все"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Разрешить"</string>
<string name="screen_room_single_knock_request_title">"%1$s хочет присоединиться к этой комнате"</string>
<string name="screen_room_single_knock_request_view_button_title">"Просмотр"</string>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Áno, prijať všetky"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Ste si istí, že chcete prijať všetky žiadosti o pripojenie?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Prijať všetky žiadosti"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Prijať všetky"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nepodarilo sa prijať všetky žiadosti. Chceli by ste to skúsiť znova?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nepodarilo sa prijať všetky žiadosti"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Prijímanie všetkých žiadostí o pripojenie"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Túto žiadosť sme nemohli prijať. Chceli by ste to skúsiť znova?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Žiadosť sa nepodarilo prijať"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Prijíma sa žiadosť o pripojenie"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Áno, odmietnuť a zakázať"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Ste si istí, že chcete odmietnuť a zakázať používateľa %1$s? Tento používateľ nebude môcť znova požiadať o prístup k tejto miestnosti."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Odmietnuť a zakázať prístup"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Odmietnutie a zákaz prístupu"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Áno, odmietnuť"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Ste si istí, že chcete odmietnuť používateľovi %1$s žiadosť o vstup do tejto miestnosti?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Odmietnuť prístup"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Odmietnuť a zakázať"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Túto žiadosť sa nepodarilo odmietnuť. Chceli by ste to skúsiť znova?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Nepodarilo sa odmietnuť žiadosť"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Odmietnutie žiadosti o prihlásenie"</string>
<string name="screen_knock_requests_list_empty_state_description">"Keď niekto požiada, aby sa pripojil k miestnosti, jeho žiadosť si môžete pozrieť tu."</string>
<string name="screen_knock_requests_list_empty_state_title">"Žiadna čakajúca žiadosť o pripojenie"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Načítavajú sa žiadosti o pripojenie…"</string>
<string name="screen_knock_requests_list_title">"Žiadosti o vstup"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti"</item>
<item quantity="few">"%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti"</item>
<item quantity="other">"%1$s +%2$d ďalších chce vstúpiť do tejto miestnosti"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Zobraziť všetko"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Prijať"</string>
<string name="screen_room_single_knock_request_title">"%1$s chce vstúpiť do tejto miestnosti"</string>
<string name="screen_room_single_knock_request_view_button_title">"Zobraziť"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, acceptera alla"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Är du säker på att du vill acceptera alla förfrågningar om att gå med?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Acceptera alla förfrågningar"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Acceptera alla"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Vi kunde inte acceptera alla förfrågningar. Vill du försöka igen?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Misslyckades att acceptera alla förfrågningar"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepterar alla förfrågningar om att gå med"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Vi kunde inte acceptera denna begäran. Vill du försöka igen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Misslyckades att acceptera förfrågan"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Accepterar begäran om att gå med"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, avslå och förbjud"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Är du säker på att du vill avvisa och förbjuda%1$s? Den här användaren kommer inte att kunna begära åtkomst för att gå med i det här rummet igen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Avvisa och förbjud åtkomst"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Avvisar och bannar åtkomst"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, avböj"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Är du säker på att du vill avslå %1$s begäran om att gå med i det här rummet?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Avvisa åtkomst"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Avvisa och förbjud"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunde inte avslå denna begäran. Vill du försöka igen?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Misslyckades att avvisa begäran"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Avvisa begäran om att gå med"</string>
<string name="screen_knock_requests_list_empty_state_description">"När någon begär om att gå med i rummet, kan du se deras förfrågan här."</string>
<string name="screen_knock_requests_list_empty_state_title">"Ingen väntande begäran om att gå med"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Laddar förfrågningar om att gå med …"</string>
<string name="screen_knock_requests_list_title">"Begäran om att gå med"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s+ %2$d annan vill gå med i detta rum"</item>
<item quantity="other">"%1$s+ %2$d andra vill gå med i detta rum"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Visa alla"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Godkänn"</string>
<string name="screen_room_single_knock_request_title">"%1$s vill gå med i det här rummet"</string>
<string name="screen_room_single_knock_request_view_button_title">"Visa"</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Evet, tümünü kabul et"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Tüm katılma isteklerini kabul etmek istediğinizden emin misiniz?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Tüm istekleri kabul et"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Tümünü kabul et"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Tüm istekleri kabul edemedik. Tekrar denemek ister misiniz?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Tüm istekler kabul edilemedi"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Tüm katılım istekleri kabul ediliyor"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Bu isteği kabul edemedik. Tekrar denemek ister misiniz?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"İstek kabul edilemedi"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Katılma isteği kabul ediliyor"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Evet, reddet ve yasakla"</string>
<string name="screen_knock_requests_list_ban_alert_description">"%1$s reddetmek ve yasaklamak istediğinizden emin misiniz? Bu kullanıcı, bu odaya tekrar katılmak için erişim isteğinde bulunamaz."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Reddet ve erişimi yasakla"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Reddediliyor ve erişim yasaklanıyor"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Evet, reddet"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Bu odaya katılma isteğini reddetmek istediğinizden emin misiniz %1$s?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Erişimi reddet"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Reddet ve yasakla"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Bu isteği reddedemedik. Tekrar denemek ister misiniz?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"İsteği reddetme başarısız oldu"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Katılma isteği reddediliyor"</string>
<string name="screen_knock_requests_list_empty_state_description">"Birisi odaya katılmak istediğinde, isteklerini burada görebileceksiniz."</string>
<string name="screen_knock_requests_list_empty_state_title">"Bekleyen katılım isteği yok"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Katılma istekleri yükleniyor…"</string>
<string name="screen_knock_requests_list_title">"Katılma istekleri"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d kişi daha bu odaya katılmak istiyor"</item>
<item quantity="other">"%1$s +%2$d kişi daha bu odaya katılmak istiyor"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Tümünü görüntüle"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Kabul et"</string>
<string name="screen_room_single_knock_request_title">"%1$s bu odaya katılmak istiyor"</string>
</resources>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Так, прийняти всі"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Ви впевнені, що хочете прийняти всі запити на приєднання?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Прийняти всі запити"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Прийняти всі"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Ми не змогли прийняти всі запити. Бажаєте спробувати ще раз?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Не вдалося прийняти всі запити"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Прийняття всіх запитів на приєднання"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Ми не змогли прийняти цей запит. Бажаєте спробувати ще раз?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Не вдалося прийняти запит"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Прийняття запиту на приєднання"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Так, відхилити та заблокувати"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Ви впевнені, що хочете відхилити та заборонити %1$s? Цей користувач не зможе знову запитувати про доступ до цієї кімнати."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Відмова та заборона доступу"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Відмова та заборона доступу"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Так, відхилити"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Ви впевнені, що хочете відхилити запит %1$s на приєднання до цієї кімнати?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Відмовити в доступі"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Відхилити та заблокувати"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Ми не змогли відхилити цей запит. Бажаєте спробувати ще раз?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Не вдалося відхилити запит"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Відхилення запиту на приєднання"</string>
<string name="screen_knock_requests_list_empty_state_description">"Коли хтось попросить приєднатися до кімнати, ви зможете побачити їхній запит тут."</string>
<string name="screen_knock_requests_list_empty_state_title">"Немає нерозглянутих запитів на приєднання"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Завантаження запитів на приєднання…"</string>
<string name="screen_knock_requests_list_title">"Запити на приєднання"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d інший хочуть приєднатися до цієї кімнати"</item>
<item quantity="few">"%1$s +%2$d інші хочуть приєднатися до цієї кімнати"</item>
<item quantity="many">"%1$s +%2$d інших хочуть приєднатися до цієї кімнати"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Переглянути все"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Прийняти"</string>
<string name="screen_room_single_knock_request_title">"%1$s хоче приєднатися до цієї кімнати"</string>
<string name="screen_room_single_knock_request_view_button_title">"Переглянути"</string>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_single_knock_request_accept_button_title">"قبول کریں"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ha, hammasini qabul qiling"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Barcha qoshilish sorovlarini qabul qilishga ishonchingiz komilmi?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Barcha sorovlarni qabul qilish"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Hammasini qabul qiling"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Biz barcha sorovlarni qabul qila olmadik. Qayta urinib koʻrmoqchimisiz?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Barcha sorovlar qabul qilinmadi"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Qoshilish sorovi qabul qilinmoqda"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Biz bu sorovni qabul qila olmadik. Yana bir bor urinib korishni xohlaysizmi?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Sorovni qabul qilib bolmadi"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Qoshilish sorovi qabul qilinmoqda"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ha, rad eting va taqiqlang"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Siz %1$sʼni rad etib, taqiqlashni xohlayotganingizga ishonchingiz komilmi? Bu foydalanuvchi ushbu xonaga qayta kirish uchun ruxsat soray olmaydi."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Rad etish va kirishni taqiqlash"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Kirishni rad etish va taqiqlash"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ha, rad etish"</string>
<string name="screen_knock_requests_list_decline_alert_description">"%1$sning bu xonaga qoshilish sorovini rad etasizmi?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Kirishni rad etish"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Rad etish va taqiqlash"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Biz bu iltimosni rad etolmasdik. Yana bir bor urinib korishni xohlaysizmi?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Sorovni rad etib bolmadi"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Qoshilish sorovi rad etilayapti"</string>
<string name="screen_knock_requests_list_empty_state_description">"Kimdir xonaga qoshilishni soraganda, uning iltimosini shu yerda korishingiz mumkin."</string>
<string name="screen_knock_requests_list_empty_state_title">"Qoshilish sorovi kutilmayapti"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Qoshilish uchun sorovlar yuklanmoqda…"</string>
<string name="screen_knock_requests_list_title">"Qoshilish uchun sorovlar"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s + %2$d kishi bu xonaga qoshilmoqchi"</item>
<item quantity="other">"%1$s + %2$d kishi bu xonaga qoshilmoqchi"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Hammasini ko\'rish"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Qabul qiling"</string>
<string name="screen_room_single_knock_request_title">"%1$s bu xonaga qoshilmoqchi"</string>
<string name="screen_room_single_knock_request_view_button_title">"Ko\'rish"</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"是的,全部接受"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"您確定要接受所有加入請求嗎?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"接受所有請求"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"全部接受"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"我們無法接受所有請求。您想要再試一次嗎?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"無法接受所有請求"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"接受所有加入請求"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"我們無法接受此請求。您想要再試一次嗎?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"無法接受請求"</string>
<string name="screen_knock_requests_list_accept_loading_title">"接受加入請求"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"是的,拒絕並封鎖"</string>
<string name="screen_knock_requests_list_ban_alert_description">"您確定要拒絕並封鎖 %1$s 嗎?此使用者將無法再次申請加入此聊天室。"</string>
<string name="screen_knock_requests_list_ban_alert_title">"拒絕並禁止存取"</string>
<string name="screen_knock_requests_list_ban_loading_title">"拒絕並封鎖存取權"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"是的,拒絕"</string>
<string name="screen_knock_requests_list_decline_alert_description">"您確定您要拒絕 %1$s 加入此聊天室的請求嗎?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"拒絕存取"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"拒絕並封鎖"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"我們無法拒絕此請求。您想要再試一次嗎?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"拒絕請求失敗"</string>
<string name="screen_knock_requests_list_decline_loading_title">"拒絕加入請求"</string>
<string name="screen_knock_requests_list_empty_state_description">"當有人要求加入聊天室時,您可以在這裡看到他們的請求。"</string>
<string name="screen_knock_requests_list_empty_state_title">"沒有待處理的加入請求"</string>
<string name="screen_knock_requests_list_initial_loading_title">"正在載入加入請求……"</string>
<string name="screen_knock_requests_list_title">"請求加入"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="other">"%1$s 與 %2$d 個其他人想要加入此聊天室"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"檢視全部"</string>
<string name="screen_room_single_knock_request_accept_button_title">"接受"</string>
<string name="screen_room_single_knock_request_title">"%1$s 想要加入此聊天室"</string>
<string name="screen_room_single_knock_request_view_button_title">"檢視"</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"是的,全部接受"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"您确定要接受所有加入请求吗?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"接受所有请求"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"全部接受"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"我们无法接受所有请求。是否要再试一次?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"无法接受所有请求"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"接受所有加入请求"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"我们无法接受此请求。是否要再试一次?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"无法接受请求"</string>
<string name="screen_knock_requests_list_accept_loading_title">"接受加入请求"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"是的,拒绝并禁止"</string>
<string name="screen_knock_requests_list_ban_alert_description">"您确定要拒绝并禁止吗%1$s该用户将无法再次请求加入该房间。"</string>
<string name="screen_knock_requests_list_ban_alert_title">"拒绝并禁止访问"</string>
<string name="screen_knock_requests_list_ban_loading_title">"拒绝并禁止访问"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"是的,拒绝"</string>
<string name="screen_knock_requests_list_decline_alert_description">"您确定要拒绝 %1$s 加入此房间的请求吗?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"拒绝访问"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"拒绝和禁止"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"我们无法拒绝此请求。是否要再试一次?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"拒绝请求失败"</string>
<string name="screen_knock_requests_list_decline_loading_title">"拒绝加入请求"</string>
<string name="screen_knock_requests_list_empty_state_description">"当有人请求加入房间时,您将能够在这里看到他们的请求。"</string>
<string name="screen_knock_requests_list_empty_state_title">"没有待处理的加入请求"</string>
<string name="screen_knock_requests_list_initial_loading_title">"正在加载加入请求…"</string>
<string name="screen_knock_requests_list_title">"申请加入"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="other">"%1$s+ %2$d 其他人想加入这个房间"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"查看全部"</string>
<string name="screen_room_single_knock_request_accept_button_title">"接受"</string>
<string name="screen_room_single_knock_request_title">"%1$s想加入这个房间"</string>
<string name="screen_room_single_knock_request_view_button_title">"查看"</string>
</resources>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Yes, accept all"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Are you sure you want to accept all requests to join?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Accept all requests"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Accept all"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"We couldnt accept all requests. Would you like to try again?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Failed to accept all requests"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepting all requests to join"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"We couldnt accept this request. Would you like to try again?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Failed to accept request"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Accepting request to join"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Yes, decline and ban"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Are you sure you want to decline and ban %1$s? This user wont be able to request access to join this room again."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Decline and ban from accessing"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Declining and banning access"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Yes, decline"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Are you sure you want to decline %1$s request to join this room?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Decline access"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Decline and ban"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"We couldnt decline this request. Would you like to try again?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Failed to decline request"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Declining request to join"</string>
<string name="screen_knock_requests_list_empty_state_description">"When somebody will ask to join the room, youll be able to see their request here."</string>
<string name="screen_knock_requests_list_empty_state_title">"No pending request to join"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Loading requests to join…"</string>
<string name="screen_knock_requests_list_title">"Requests to join"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d other want to join this room"</item>
<item quantity="other">"%1$s +%2$d others want to join this room"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"View all"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Accept"</string>
<string name="screen_room_single_knock_request_title">"%1$s wants to join this room"</string>
<string name="screen_room_single_knock_request_view_button_title">"View"</string>
</resources>

View File

@@ -0,0 +1,244 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
import com.google.common.truth.Truth.assertThat
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) class KnockRequestsBannerPresenterTest {
@Test
fun `present - when feature is disabled then the banner should be hidden`() = runTest {
val knockRequests = flowOf(listOf(FakeKnockRequest()))
val presenter = createKnockRequestsBannerPresenter(isFeatureEnabled = false, knockRequestsFlow = knockRequests)
presenter.test {
skipItems(1)
awaitItem().also { state ->
assertThat(state.isVisible).isFalse()
}
}
}
@Test
fun `present - when empty knock request list then the banner should be hidden`() = runTest {
val knockRequests = flowOf(emptyList<KnockRequest>())
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
presenter.test {
skipItems(1)
awaitItem().also { state ->
assertThat(state.isVisible).isFalse()
}
}
}
@Test
fun `present - when no permission to manage knock requests then the banner should be hidden`() = runTest {
val presenter = createKnockRequestsBannerPresenter(canAcceptKnockRequests = false)
presenter.test {
awaitItem().also { state ->
assertThat(state.isVisible).isFalse()
}
}
}
@Test
fun `present - when everything is setup to manage knocks with data, then the banner should be visible`() = runTest {
val knockRequests = flowOf(
listOf(
FakeKnockRequest(
reason = "A reason",
)
)
)
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
presenter.test {
skipItems(2)
awaitItem().also { state ->
assertThat(state.isVisible).isTrue()
assertThat(state.knockRequests).hasSize(1)
assertThat(state.canAccept).isTrue()
assertThat(state.reason).isEqualTo("A reason")
}
}
}
@Test
fun `present - when multiple knock requests, the banner should not have reason nor subtitle`() = runTest {
val knockRequests = flowOf(
listOf(
FakeKnockRequest(
displayName = "Alice",
),
FakeKnockRequest(
displayName = "Bob",
),
FakeKnockRequest(
displayName = "Charlie",
),
)
)
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
presenter.test {
skipItems(2)
awaitItem().also { state ->
assertThat(state.isVisible).isTrue()
assertThat(state.knockRequests).hasSize(3)
assertThat(state.reason).isNull()
assertThat(state.subtitle).isNull()
}
}
}
@Test
fun `present - when there are some seen knock requests, then the banner should filtered them`() = runTest {
val knockRequests = flowOf(
listOf(
FakeKnockRequest(
displayName = "Alice",
isSeen = true,
userId = A_USER_ID
),
FakeKnockRequest(
displayName = "Bob",
isSeen = true,
userId = A_USER_ID_2
),
FakeKnockRequest(
isSeen = false,
displayName = "Charlie",
reason = "A reason",
userId = A_USER_ID_3
),
)
)
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
presenter.test {
skipItems(2)
awaitItem().also { state ->
assertThat(state.isVisible).isTrue()
// Only Charlie should be displayed
assertThat(state.knockRequests).hasSize(1)
assertThat(state.reason).isEqualTo("A reason")
assertThat(state.subtitle).isEqualTo(A_USER_ID_3.value)
}
}
}
@Test
fun `present - given AcceptSingleRequest event with failure, then the banner should hide and reappear and error should appear and disappear`() = runTest {
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.failure(Exception()) }
val knockRequest = FakeKnockRequest(
displayName = "Alice",
reason = "A reason",
acceptLambda = acceptLambda
)
val knockRequests = flowOf(listOf(knockRequest))
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
presenter.test {
skipItems(2)
awaitItem().also { state ->
state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest)
}
awaitItem().also { state ->
assertThat(state.isVisible).isFalse()
assertThat(state.displayAcceptError).isFalse()
}
awaitItem().also { state ->
assertThat(state.isVisible).isFalse()
assertThat(state.displayAcceptError).isTrue()
}
awaitItem().also { state ->
assertThat(state.isVisible).isTrue()
assertThat(state.displayAcceptError).isTrue()
}
awaitItem().also { state ->
assertThat(state.isVisible).isTrue()
assertThat(state.displayAcceptError).isFalse()
}
assert(acceptLambda).isCalledOnce()
}
}
@Test
fun `present - given an AcceptSingleRequest event with success, then banner should be dismissed`() = runTest {
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val knockRequest = FakeKnockRequest(
displayName = "Alice",
reason = "A reason",
acceptLambda = acceptLambda
)
val knockRequests = flowOf(listOf(knockRequest))
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
presenter.test {
skipItems(2)
awaitItem().also { state ->
assertThat(state.knockRequests).hasSize(1)
state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest)
}
awaitItem().also { state ->
assertThat(state.isVisible).isFalse()
}
advanceUntilIdle()
assert(acceptLambda).isCalledOnce()
}
}
@Test
fun `present - given a Dismiss event, then knock requests should be marked as seen`() = runTest {
val markAsSeenLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val knockRequests = flowOf(
listOf(
FakeKnockRequest(markAsSeenLambda = markAsSeenLambda),
FakeKnockRequest(markAsSeenLambda = markAsSeenLambda),
FakeKnockRequest(markAsSeenLambda = markAsSeenLambda),
)
)
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
presenter.test {
skipItems(2)
awaitItem().also { state ->
state.eventSink(KnockRequestsBannerEvents.Dismiss)
}
advanceUntilIdle()
assert(markAsSeenLambda).isCalledExactly(3)
}
}
}
private fun TestScope.createKnockRequestsBannerPresenter(
knockRequestsFlow: Flow<List<KnockRequest>> = flowOf(emptyList()),
canAcceptKnockRequests: Boolean = true,
isFeatureEnabled: Boolean = true,
): KnockRequestsBannerPresenter {
val knockRequestsService = KnockRequestsService(
knockRequestsFlow = knockRequestsFlow,
coroutineScope = backgroundScope,
isKnockFeatureEnabledFlow = flowOf(isFeatureEnabled),
permissionsFlow = flowOf(KnockRequestPermissions(canAcceptKnockRequests, canAcceptKnockRequests, canAcceptKnockRequests)),
)
return KnockRequestsBannerPresenter(
knockRequestsService = knockRequestsService,
sessionCoroutineScope = this,
)
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.banner
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class KnockRequestsBannerViewTest {
@get:Rule
val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `clicking on view on single request invoke the expected callback`() {
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
ensureCalledOnce {
rule.setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
eventSink = eventsRecorder,
),
onViewRequestsClick = it
)
rule.clickOn(R.string.screen_room_single_knock_request_view_button_title)
}
}
@Test
fun `clicking on view all when multiple requests invoke the expected callback`() {
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
ensureCalledOnce {
rule.setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
knockRequests = listOf(
aKnockRequestPresentable(displayName = "Alice"),
aKnockRequestPresentable(displayName = "Bob"),
aKnockRequestPresentable(displayName = "Charlie")
),
eventSink = eventsRecorder,
),
onViewRequestsClick = it
)
rule.clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title)
}
}
@Test
fun `clicking on accept on a single request emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
rule.setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
eventSink = eventsRecorder,
),
)
rule.clickOn(CommonStrings.action_accept)
eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest)
}
@Test
fun `clicking on dismiss emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
rule.setKnockRequestsBannerView(
state = aKnockRequestsBannerState(
eventSink = eventsRecorder,
),
)
val close = rule.activity.getString(CommonStrings.action_close)
rule.onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setKnockRequestsBannerView(
state: KnockRequestsBannerState,
onViewRequestsClick: () -> Unit = EnsureNeverCalled(),
) {
setContent {
KnockRequestsBannerView(
state = state,
onViewRequestsClick = onViewRequestsClick,
)
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext
import com.google.common.truth.Truth.assertThat
import io.element.android.tests.testutils.node.TestParentNode
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class DefaultKnockRequestsListEntryPointTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun `test node builder`() = runTest {
val entryPoint = DefaultKnockRequestsListEntryPoint()
val parentNode = TestParentNode.create { buildContext, plugins ->
KnockRequestsListNode(
buildContext = buildContext,
plugins = plugins,
presenter = createKnockRequestsListPresenter(),
)
}
val result = entryPoint.createNode(parentNode, BuildContext.root(null))
assertThat(result).isInstanceOf(KnockRequestsListNode::class.java)
}
}

View File

@@ -0,0 +1,305 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.knockrequests.impl.list
import com.google.common.truth.Truth.assertThat
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class KnockRequestsListPresenterTest {
@Test
fun `present - initial states should be emitted`() = runTest {
val presenter = createKnockRequestsListPresenter()
presenter.test {
awaitItem().also { state ->
assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java)
assertThat(state.permissions.canAccept).isFalse()
assertThat(state.permissions.canDecline).isFalse()
assertThat(state.permissions.canBan).isFalse()
}
awaitItem().also { state ->
assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java)
assertThat(state.permissions.canAccept).isTrue()
assertThat(state.permissions.canDecline).isTrue()
assertThat(state.permissions.canBan).isTrue()
}
awaitItem().also { state ->
assertThat(state.knockRequests).isInstanceOf(AsyncData.Success::class.java)
assertThat(state.knockRequests.dataOrNull()).isEmpty()
}
}
}
@Test
fun `present - accept success scenario`() = runTest {
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val knockRequest = FakeKnockRequest(acceptLambda = acceptLambda)
val knockRequests = flowOf(listOf(knockRequest))
val presenter = createKnockRequestsListPresenter(
knockRequestsFlow = knockRequests
)
presenter.test {
skipItems(2)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable))
}
skipItems(1)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Accept(knockRequestPresentable))
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
}
skipItems(2)
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
}
assert(acceptLambda).isCalledOnce()
}
}
@Test
fun `present - accept failure scenario`() = runTest {
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.failure(Exception()) }
val knockRequest = FakeKnockRequest(acceptLambda = acceptLambda)
val knockRequests = flowOf(listOf(knockRequest))
val presenter = createKnockRequestsListPresenter(
knockRequestsFlow = knockRequests
)
presenter.test {
skipItems(2)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable))
}
skipItems(1)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Accept(knockRequestPresentable))
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java)
state.eventSink(KnockRequestsListEvents.RetryCurrentAction)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java)
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
}
skipItems(1)
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
assertThat(state.knockRequests.dataOrNull()).hasSize(1)
}
assert(acceptLambda).isCalledExactly(2)
}
}
@Test
fun `present - decline success scenario`() = runTest {
val declineLambda = lambdaRecorder<String?, Result<Unit>> { Result.success(Unit) }
val knockRequest = FakeKnockRequest(declineLambda = declineLambda)
val knockRequests = flowOf(listOf(knockRequest))
val presenter = createKnockRequestsListPresenter(
knockRequestsFlow = knockRequests
)
presenter.test {
skipItems(2)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
state.eventSink(KnockRequestsListEvents.Decline(knockRequestPresentable))
}
skipItems(1)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Decline(knockRequestPresentable))
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
}
skipItems(2)
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
}
}
assert(declineLambda).isCalledOnce()
}
@Test
fun `present - decline and ban success scenario`() = runTest {
val declineAndBanLambda = lambdaRecorder<String?, Result<Unit>> { Result.success(Unit) }
val knockRequest = FakeKnockRequest(declineAndBanLambda = declineAndBanLambda)
val knockRequests = flowOf(listOf(knockRequest))
val presenter = createKnockRequestsListPresenter(
knockRequestsFlow = knockRequests
)
presenter.test {
skipItems(2)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequestPresentable))
}
skipItems(1)
awaitItem().also { state ->
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.DeclineAndBan(knockRequestPresentable))
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
}
skipItems(2)
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
}
}
assert(declineAndBanLambda).isCalledOnce()
}
@Test
fun `present - accept all success scenario`() = runTest {
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val knockRequests = flowOf(
listOf(
FakeKnockRequest(eventId = AN_EVENT_ID, acceptLambda = acceptLambda),
FakeKnockRequest(eventId = AN_EVENT_ID_2, acceptLambda = acceptLambda),
)
)
val presenter = createKnockRequestsListPresenter(
knockRequestsFlow = knockRequests
)
presenter.test {
skipItems(2)
awaitItem().also { state ->
assertThat(state.canAcceptAll).isTrue()
state.eventSink(KnockRequestsListEvents.AcceptAll)
}
skipItems(1)
awaitItem().also { state ->
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll)
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
}
skipItems(2)
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
}
}
assert(acceptLambda).isCalledExactly(2)
}
@Test
fun `present - accept all partial success scenario`() = runTest {
val acceptSuccessLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val acceptFailureLambda = lambdaRecorder<Result<Unit>> { Result.failure(Exception()) }
val knockRequests = flowOf(
listOf(
FakeKnockRequest(eventId = AN_EVENT_ID, acceptLambda = acceptSuccessLambda),
FakeKnockRequest(eventId = AN_EVENT_ID_2, acceptLambda = acceptFailureLambda),
)
)
val presenter = createKnockRequestsListPresenter(
knockRequestsFlow = knockRequests
)
presenter.test {
skipItems(2)
awaitItem().also { state ->
assertThat(state.canAcceptAll).isTrue()
state.eventSink(KnockRequestsListEvents.AcceptAll)
}
skipItems(1)
awaitItem().also { state ->
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll)
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
}
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java)
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
}
skipItems(2)
awaitItem().also { state ->
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
assertThat(state.knockRequests.dataOrNull()).hasSize(1)
}
}
assert(acceptFailureLambda).isCalledOnce()
assert(acceptSuccessLambda).isCalledOnce()
}
}
internal fun TestScope.createKnockRequestsListPresenter(
canAccept: Boolean = true,
canDecline: Boolean = true,
canBan: Boolean = true,
knockRequestsFlow: Flow<List<KnockRequest>> = flowOf(emptyList())
): KnockRequestsListPresenter {
val knockRequestsService = KnockRequestsService(
knockRequestsFlow = knockRequestsFlow,
coroutineScope = backgroundScope,
isKnockFeatureEnabledFlow = flowOf(true),
permissionsFlow = flowOf(KnockRequestPermissions(canAccept, canDecline, canBan)),
)
return KnockRequestsListPresenter(knockRequestsService = knockRequestsService)
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.knockrequests.impl.list
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.knockrequests.impl.R
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
import io.element.android.tests.testutils.ensureCalledOnce
import io.element.android.tests.testutils.pressBack
import kotlinx.collections.immutable.persistentListOf
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestRule
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class KnockRequestsListViewTest {
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
@Test
fun `clicking on back invoke the expected callback`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>(expectEvents = false)
ensureCalledOnce {
rule.setKnockRequestsListView(
aKnockRequestsListState(
eventSink = eventsRecorder,
),
onBackClick = it
)
rule.pressBack()
}
}
@Test
fun `clicking on accept emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
val knockRequest = aKnockRequestPresentable()
rule.setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
eventSink = eventsRecorder,
),
)
rule.clickOn(CommonStrings.action_accept)
eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest))
}
@Test
fun `clicking on decline emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
val knockRequest = aKnockRequestPresentable()
rule.setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
eventSink = eventsRecorder,
),
)
rule.clickOn(CommonStrings.action_decline)
eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest))
}
@Test
fun `clicking on decline and ban emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
val knockRequest = aKnockRequestPresentable()
rule.setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
eventSink = eventsRecorder,
),
)
rule.clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title)
eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest))
}
@Test
fun `clicking on accept all emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
rule.setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
eventSink = eventsRecorder,
),
)
rule.clickOn(R.string.screen_knock_requests_list_accept_all_button_title)
eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll)
}
@Test
fun `retry on async view retry emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
rule.setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
currentAction = KnockRequestsAction.AcceptAll,
eventSink = eventsRecorder,
),
)
rule.clickOn(CommonStrings.action_retry)
eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction)
}
@Test
fun `canceling async view emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
rule.setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
currentAction = KnockRequestsAction.AcceptAll,
eventSink = eventsRecorder,
),
)
rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction)
}
@Test
fun `confirming async view emit the expected event`() {
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
rule.setKnockRequestsListView(
aKnockRequestsListState(
knockRequests = AsyncData.Success(knockRequests),
asyncAction = AsyncAction.ConfirmingNoParams,
currentAction = KnockRequestsAction.AcceptAll,
eventSink = eventsRecorder,
),
)
rule.clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title)
eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction)
}
}
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setKnockRequestsListView(
state: KnockRequestsListState,
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
setContent {
KnockRequestsListView(
state = state,
onBackClick = onBackClick,
)
}
}